Оживляем таблицы с помощью RxDataSources. RxSwift часть 1

Разрабатывая под iOS, меня всегда смущало обилие делегатов для создания таблиц и их громоздкость. Как только я начал использовать RxSwift, мне удалось познакомиться с его компонентом - RxDataSources. И теперь могу точно сказать, что работа с таблицами еще никогда не была такой простой.
RxDataSources
Представьте себе ситуацию, где вам нужно создать таблицу, в которой будет несколько секций, а в каждой из этих секций могут быть разные типы ячеек. Да-да, все знают это чувство рутины, которое появляется при создании таблиц или коллекций. Куча шаблонного кода без возможности его как-то ужать.

RxSwift позволяет если и не избавиться от этой проблемы, то хотя бы облегчить ее посредством оборачивания DataSource в Observable и привязывания его к таблице или коллекции.

Есть же случаи, когда этого недостаточно, а именно, когда нам нужно построить сложную таблицу с различными ячейками и секциями, или же когда нужно анимировать удаление, добавление или перемещение ячеек. Для этого существует RxDataSources.
Анимированное редактирование таблицы
Рассмотрим анимирование на простом примере с таблицей, где можно будет перемещать, удалять или выбирать ячейки, чтобы перейти на экран деталей.
В первой таблице будет один тип ячейки, но так как она будет интерактивной, то будем использовать RxDataSources (к тому же, если вы уже решили интегрировать RxSwift в свой проект, то лучше будет его использовать везде и постоянно).

Для простой таблицы нужно начать с объявления SectionModel<Section, ItemType>, где ItemType это тип элемента вашего DataSource. Для анимированной таблицы нужно будет использовать AnimatableSectionModel<Section, ItemType>. В зависимости от того, какого типа мы объявляем модель, ее Item должен соответствовать определенному протоколу:

  • В первом случае достаточно Equatable
  • Во втором к Equatable нужно добавить IdentifiableType
На IdentifiableType нужно остановиться подробнее, так как если пройтись по вопросам на гитхабе RxDataSource, то можно заметить, что не всегда люди понимают в чем дело и почему у них интерфейс неправильно себя ведет.
Identity - это в каком-то смысле social security number объекта. Константа, которая поможет определить личность объекта, например UUID.

Equatable помогает понять, когда у объекта поменялось какое-то свойство. Любое указанное изменение будет триггерить обновление ячейки.

За примером далеко идти не нужно, взять человека: он может поменять одежду, сделать стрижку и сходить в солярий, но его личность останется той же, и для идентификации будет использоваться соответствующий документ.
Объявляем typealias AnimatableSectionModel и dataSource:
Инициализируем dataSource с нужными параметрами:

  • AnimationConfiguration - указываем типы анимаций для разных действий
  • ConfigureCell - возвращаем ячейку и привязываем ее ко viewModel, здесь же можно удобно привязать какие-то действия на элементы в ячейке, например, действие кнопок
  • CanEditRowAtIndexPath - разрешено ли редактировать ячейку
  • CanMoveRowAtIndexPath - разрешено ли перемещать ячейку. Указывается всегда после CanEditRowAtIndexPath
Далее привязываем DataSource к SectionModel, конечно же, стараемся использовать Driver:
Для удаления используем расширение rx.itemDeleted, где при удалении возвращается indexPath удаляемой ячейки.
Для перемещения берем rx.itemMoved, где перемещая ячейку мы получаем indexPath, по которому находилась ячейка, и indexPath назначения:
Видно, что recyclerView перехватывает свайп у MotionLayout. Чтобы этого избежать, добавляем recyclerView следующий параметр:
И, конечно, rx.modelSelected, где возвращается указанный объект, в выбранной ячейке:
Смотрим результат:
Complex tables
Создание таблиц с разными ячейками и множественными секциями не сильно отличается от того, что описывалось выше. Главным отличием будет то, что вместо обозначения SectionModel нужно создать два типа суммы:

  • для секций этот тип суммы должен соответствовать протоколу SectionModelType. Associated value - массив типа суммы для ячеек
  • для ячеек здесь associated value это то, что вы хотите передать в ячейку
В нашем примере будет окно деталей рецепта, где будет три вида ячеек в одной секции:
Во viewModel создаем Observable типа суммы секций и инициализируем его каждый раз, когда приходит какое-то изменение в DataSource:
В ConfigureCell возвращаем необходимую ячейку с помощью switch:
Смотрим результат:
Спасибо за внимание!