@згадування в чатах і соцмережах часто працюють ненадійно: ламаються при зміні username, не надсилають сповіщення, або пошук користувачів гальмує. Один з наших клієнтів втратив 15% посилань після перейменування учасників — довелося переписувати систему з нуля. За 5 років ми реалізували понад 15 проєктів із згадуваннями для соцмереж та корпоративних чатів, заощаджуючи клієнтам до 40% часу на розробку. Правильно спроєктована система згадок знижує кількість багів на 30% і підвищує точність пошуку на 25%. У цій статті розберемо, як побудувати систему, яка витримує 1000 згадок на хвилину і не втрачає посилання. Синтаксис @згадувань визначений у документації, але на практиці складність у парсингу, автодоповненні та сповіщеннях.
Як працює автодоповнення користувачів?
Сценарій: користувач пише @ал — показуємо список відповідних імен. Логіка відстеження курсора аналогічна хештегам: шукаємо @ перед курсором.
Пошук користувачів
GET /users/search?q=ал&limit=10 — пошук за username та display_name. На стороні БД — WHERE username ILIKE 'ал%' OR display_name ILIKE 'ал%' з GIN-індексом pg_trgm для нечіткого пошуку. Дебаунс 150-200ms на клієнті.
Пріоритизація результатів: спочатку взаємні підписники, потім усі інші. JOIN з follows для сортування за is_mutual.
UI dropdown на iOS
func textViewDidChange(_ textView: UITextView) {
guard let mentionQuery = extractMentionQuery(textView) else {
hideMentionSuggestions()
return
}
searchUsers(query: mentionQuery)
.debounce(for: .milliseconds(200), scheduler: RunLoop.main)
.sink { [weak self] users in
self?.showMentionSuggestions(users)
}
.store(in: &cancellables)
}
func extractMentionQuery(_ textView: UITextView) -> String? {
let text = textView.text as NSString
let cursorPosition = textView.selectedRange.location
let searchRange = NSRange(location: 0, length: cursorPosition)
let pattern = "@([\\w\\.]{0,30})$"
guard let match = try? NSRegularExpression(pattern: pattern)
.firstMatch(in: textView.text, range: searchRange) else { return nil }
return (text.substring(with: match.range(at: 1)))
}
При виборі користувача зі списку — вставляємо @username як єдиний токен (замінюємо частково введений текст). Токен не розбивається при редагуванні — при видаленні одного символу видаляється весь @username. На iOS — через NSAttributedString з кастомним атрибутом і перехопленням shouldChangeTextIn. На Compose — TextFieldValue з AnnotatedString, перехоплення змін через onValueChange.
Чому важливо зберігати user_id, а не username?
Проблема: зберігати @username як текст погано — якщо користувач змінить username, згадка зламається. Правильно зберігати згадку як пару (user_id, display_username_at_time):
- У тілі посту:
"Привіт @[user:42|alex]! Як справи?"— кастомний синтаксис зuser_id. - При відображенні: беремо актуальний username користувача 42 з БД, показуємо клікабельним.
- Якщо користувач видалив обліковий запис — показуємо
@видалений_акаунтсірим.
Парсинг при відображенні додає навантаження — кешуємо display_name за user_id в пам'яті на час сесії.
Порівняння підходів:
| Підхід | Стійкість до зміни username | Навантаження при відображенні | Складність реалізації |
|---|---|---|---|
| Зберігання username | Низька (ламається) | Низька | Низька |
| Зберігання user_id + кеш | Висока (не ламається) | Середня (кеш) | Середня |
Зберігання user_id в 3 рази надійніше при зміні імені — це підтверджує практика наших проєктів.
Що таке кастомний синтаксис згадок?
Кастомний синтаксис @[user:42|alex] дозволяє прив'язати згадку до конкретного користувача незалежно від його поточного імені. Це вирішує проблему зміни username та видалення акаунта. Парсинг такого синтаксису на стороні клієнта: при отриманні повідомлення витягуємо user_id з токенів, підставляємо актуальне ім'я з кешу та робимо посилання на профіль. На сервері при публікації зберігаємо вихідний текст з токенами, а при відображенні генеруємо HTML/AttributedString з підстановкою.
Порівняння платформ для реалізації автодоповнення:
| Платформа | Компонент | Відстеження курсора | Вставка токена |
|---|---|---|---|
| iOS (Swift) | UITextView + UITextInputDelegate | NSRange через selectedRange | NSMutableAttributedString з NSTextAttachment |
| Android (Compose) | BasicTextField + onValueChange | TextFieldValue.selection | AnnotatedString з pushStringAnnotation |
| Flutter | TextField + controller | TextEditingValue.selection | TextSpan з GestureRecognizer |
Як реалізувати сповіщення без втрати продуктивності?
При публікації посту або коментаря із згадкою — парсимо user_id з токенів, створюємо записи в notifications (recipient_id, type='mention', source_post_id, actor_id), надсилаємо push через FCM/APNs. Батчинг: якщо в одному пості згадано 5 осіб — 5 окремих сповіщень (кожному своє). Якщо користувача згадано кілька разів за хвилину різними людьми — одне сповіщення «3 згадки». Користувач може вимкнути сповіщення про згадки в налаштуваннях — прапорець notify_mentions у профілі.
Профіль по тапу на згадку
Тап на @username → відкриваємо профіль користувача. Якщо користувача заблоковано або він заблокував мене — показуємо заглушку «Профіль недоступний». Реалізація через UITextViewDelegate.textView(_:shouldInteractWith:in:interaction:) (iOS) або ClickableText з LocalUriHandler (Compose). Для Android використовуємо анотації: при парсингу повідомлення замінюємо токени @[user:42] на AnnotatedString.Range з URL-схемою profile://42, потім у ClickableText перехоплюємо натискання та викликаємо навігацію.
Етапи роботи
- Парсинг та підсвічування
@згадоку тексті - Автодоповнення з пошуком через API та дебаунсом
- Зберігання згадок з прив'язкою до user_id
- Push-сповіщення з батчингом
- Документація API та схема БД
- Інтеграція з існуючим чатом/соцмережею
- Тестування на реальних сценаріях (до 1000 згадок на хвилину)
- Підтримка після впровадження
Що входить у роботу
- Консультація щодо архітектури та вибору синтаксису
- Парсинг та підсвічування згадок у тексті
- Автодоповнення з дебаунсом та пріоритизацією
- Зберігання з user_id та кешуванням
- Push-сповіщення з батчингом
- Інтеграція з існуючим чатом або соцмережею
- Документація по API та схемі БД
- Тестування продуктивності (до 1000 згадок на хвилину)
- Підтримка протягом 2 тижнів після впровадження
Строки
Парсинг згадок та підсвічування в існуючому тексті — 1 день. Автодоповнення при введенні з пошуком користувачів — 1-2 дні. Сповіщення — ще 1 день. Повна система — 3-5 робочих днів. Вартість розраховується індивідуально, економія на розробці завдяки готовим модулям може скласти до 50% часу.
Зв'яжіться з нами, щоб обговорити інтеграцію. Напишіть на пошту або замовте зворотний дзвінок — ми оцінимо ваш проєкт безкоштовно та запропонуємо оптимальне рішення під ключ. Наша команда має 5+ років досвіду в розробці мобільних додатків та сертифікована за стандартами Apple і Google. Гарантуємо якість та дотримання строків. Отримайте консультацію інженера вже сьогодні.







