Чат своїми руками

Чат своїми руками +4

  • 02.06.17 7:08 •
  • SSul •
  • # 330044 •
  • Хабрахабр •
  • 0 •
  • 2600

- такий же як Forbes, тільки краще.

У цій статті ми опишемо тонкощі написання чату. Розумію, що придумано вже досить готових рішень. Побродивши по закутках безкрайнього, відшукали пару придатних бібліотек, які надають чат «через коробки». У цій статті вони перераховані не будуть. Так ось, перспектива заюзать готове рішення здавалася привабливою. Але, ще раз подумавши про комплексності майбутнього завдання, ми вирішили писати з нуля.


Завдання не зовсім типова, і вимоги по проекту до кінця не визначені. Ризиковано заганяти себе в рамки невикористаної раніше бібліотеки. Чого, звичайно, не скажеш про перевірених в бою лібах начебто RxDataSources, яку ми неодмінно вирішили використовувати. Якщо щось трапиться, в будь-який момент розробки вона легко заміщується вручну прописаної логікою по роботі з осередками. Також не обійшлося без насущного «джентльменського наборчика iOS-розробника»: RxSwift, Realm, Alamofire і Kingfisher. Їх ми також підключили до проекту.

Список повідомлень реалізовували через UICollectionView через його триразової гнучкості. І анімована колекція в UIKit красивіше ніж таблиця. При цьому анімацією можна управляти. Від Storyboard'a в рамках чату відразу відмовилися, тому як Autolayout і продуктивність - речі, якщо не з різних галактик, то, як мінімум, один про одного не знають.

Все це говорить про те, що архітектура повідомлень повинна бути спроектована особливо ретельно.

Чат своїми руками


Контент розміщується ближче до певного краю екрану в залежності від типу повідомлення «своє-чуже». Висота стандартно розраховується за допомогою шаблонної осередки в залежності від вищеописаної конфігурації. Тут же розраховується відстань між осередками. У CollectionViewLayout воно дорівнює нулю, відстань просто додається до висоти комірки, щоб логіка не розносився по об'єктах.

Технічна складність проявилася в посторінковою завантаженні. Десяток повідомлень вантажився половину секунди на iPhone 6 - дуже слабкий результат. Насправді, нічого дивного: осередки важкі і весь процес їх створення і наповнення відбувався в cellForRow. Від цього Main-потік терпів лихо. Досить швидко прийшла ідея створювати вьюха з контентом відразу після отримання повідомлень в Background-потоці. Деякі скажуть, що так не можна, і матимуть рацію. UILabel в тлі створити так і не вийшло, додаток просто фарбований. Решта UIView-примітиви спокійно дозволяють створювати себе в бекграунд, причому, саме клас UIView дозволений до створення в тлі офіційної документацією.

Практика показала, що фрейми у об'єктів також можна задавати з фону. Цього нам вистачило, щоб розмазати створення осередку з повідомленням і винести логіку розрахунку розміру осередку в фон. Також, варто відзначити, що, змістивши додавання всіх сабвьюх в клітинку на момент willDisplayCell, ми помітно підвищили плавність подгрузки. І в цьому методі ми даємо зрозуміти контент-вьюха, що вона нарешті потрапила в Main-потік, а, значить, тут можна налаштувати всі аутлети (створити UILabel'и) і підставити в них контент. По суті, відразу після отримання повідомлення, в тлі йде формування розміру осередку, далі її генерація в цьому розмірі, а в самий останній момент перед відображенням отрисовка всього контенту на ній:

Чат своїми руками


В осередках повинен бути мінімум прозорості. Залозу девайсів складно перетравлювати змішані шари, а тому напівпрозорі вьюха - бич продуктивності, і будь-який складний компонент повинен бути від нього позбавлений. Обрізка кутів на вьюха все одно робить їх напівпрозорими. Для наочності можна в симуляторі пристрою виставити галочку на Color blended layers, і він Отріс їх червоним кольором. Тут нічого не поробиш, принаймні нічого, що можна придумати за короткий час.

Єдиний мінус застосованого підходу - зберігання посилань на вьюха з контентом.

Раз створити, в'ю вже ніколи не деаллоціровалась, хіба що після виходу з екрану. Це рішення було прийнято усвідомлено. Це позбавило нас від проблем з продуктивністю. Рітейн вьюха дозволив прораховувати розмір і створювати їх лише один раз і в тлі. На пам'яті це майже ніяк не позначилося, головний принцип - не потягти за собою картинки. Поле image у UIImageView має обов'язково обнулятиметься в didEndDisplayingCell і знову знаходити значення лише при новому відображенні осередки.

Вуаля, і все плавно! Жарт. Плавно в Телеграма. Але для наших термінів дуже непогана продуктивність. Навіть на мінімально підтримуваному девайсе подгрузка ледве помітна, а на сучасних девайсах і зовсім не видно. Подальше додаток до типам повідомлень не позначилося на продуктивності подгрузки.

І нічого не віщувало біди, як раптом зламався CollectionViewLayout. Ну ви розумієте, як це зазвичай буває: збивається контент сайз, осередки стають невидимими і т.д і ніякої reloadData () в цьому випадку не рятує ... зламався і все тут? \ _ (?) _ /. Траплялося це в невизначений момент часу, просто при Скролл чату. Спробували поміняти вставку одиночних осередків на повне перезавантаження колекції, позбутися від RxDataSources, перенести створення уявлень в головний потік - все без толку. Сталося це після обширного Мержа гілок, тому розібратися, чим саме був викликаний брейк, з ходу не вийшло. Випадок був цікавий, і хотілося дістатися до істини. Замовник хотів збірку.

«TableView, друже, ми так за тобою нудьгували! ... в сенсі зрадники?»

Пішла буквально пара годин, перш ніж колекцію вдалося повністю замінити на таблицю. Не було абсолютної впевненості в тому, що це вирішить проблему. Але TableView відпрацьовував ідеально. Здавалося, що працював він навіть трохи швидше, ніж колекція. Але анімація інсерта осередків залишає бажати кращого. У чаті вона зовсім виглядає неприйнятно, тому було вирішено оновлювати список через reloadData (). А поломка CollectionView так і залишилася загадкою: це один з найскладніших елементів в UIKit'е, і розібратися в ньому за вихідні не вдалося. Головне, що завдання виконане - база чату реалізована на належному рівні.

Продуктивність - тема глибока і цікава. Але далеко не єдина, з якою ми зіткнулися. Були ще цікаві нюанси. Той же переворот таблиці.

Як всі знають, повідомлення в чаті починаються знизу, а у колекцій і таблиць немає відповідного функціоналу для подібного відображення осередків. Тут ми використовували CGAffineTransform: перевернули таблицю догори дном, а осередки перевернули ще раз - все ідеально, і без будь-яких можливих багів і проблем з продуктивністю.

Виштовхування дати текстом реалізували через додавання в текст повідомлення нерозривних пробілів довжиною трохи більше ніж довжина мітки з датою. Якщо прогалини діставали до краю повідомлення - значить і оригінальний текст стосувався дати і в цьому випадку текст переносився на наступний рядок збільшуючи висоту комірки, яка, в свою чергу, тягла за собою мітку з датою. З такою фішкою повідомлення виглядають більш компактно:

Чат своїми руками


Варто сказати пару слів про AutoresizingMask. Деякі недооцінюють її важливість, а при верстці з коду корисно розуміти, як працює ця технологія. У нашому випадку були осередки, які розтягувалися і стискалися за подією. І, щоб кожного разу не відновлювати фрейми, маски були задані так, що уявлення змінювали свій розмір відповідно до розмірів своїх батьків. Без єдиного констрейнтів і рядки коду - чиста магія. Нижче представлений приклад її використання: спочатку маємо осередок, далі ми збільшуємо її висоту (без використання масок контент і залишився б в такому положенні), після чого, в правий нижній кут, підтягується дата, а весь інший контент розтягується:

Чат своїми руками


Наступний нюанс: при появі клавіатури для можливості побачити повідомлення, які залишилися під нею у таблиці виставлявся нижній інсет. Ще він виставлявся зверху, в разі якщо спливала плашка про відсутність інтернету, і знизу, коли поле для введення повідомлення змінювало свою висоту, все це не рахуючи верхній і нижній інсет по дефолту. Віднімати і додавати в кожному місці потрібну константу - досить крихке рішення і зламається якщо хоча б один метод зголоситься зайвий раз. В якості вирішення була написана обгортка для UITableView, що містить масив структур з інсетамі по кожній причини. Причини були описані в перерахуванні, а кожен раз при зміні масиву все відступи складалися і привласнювалися таблиці. Таким чином, з'явилася можливість управляти кожним інсетом, не ламаючи інші. Варто згадати: все відступи в нашому випадку були перевернуті разом з таблицею через застосованих раніше трансформацій, тому верхній інсет був нижнім і навпаки.

Цікавих моментів і відкриттів при розробці було більш ніж достатньо, всіх не згадаєш. Повторюся, можна вкласти нескінченну кількість годин, роблячи додаток все більш плавним, швидким і гнучким. Але це, разом з візуальною частиною, - перша стадія, необхідний мінімум для успішного застосування чату. До наступному ступені можна віднести унікальність досвіду його використання. Чого далеко ходити, візьмемо нативний ios-месенджер iMessage. Крім чуйності, у месенджера безліч "модних фішок", більшості яких просто ніде немає. Для комунікації вони не настільки важливі, але якщо розглядати чат як окрему сутність, володар таких "фішок" виглядає більш досконалим і розвиненим на фоні інших. Етап реалізації таких особливостей в рідкісних випадках можна віднести до першої версії додатка. Тому, випустивши першу версію чату, ми сподіваємося в майбутньому продовжити роботу і наповнити чат унікальними особливостями.

Ви можете допомогти і перевести трохи коштів на розвиток сайту

Схожі статті