Ускорьте разработку табличных представлений в Swift 5
В этой статье мы узнаем, как сделать создание табличных представлений и их ячеек быстрее и проще. Мы сделаем это, используя протоколы, наследование и абстракцию. К концу статьи у вас будет готовый к работе механизм, который вы сможете свободно интегрировать в свои приложения для iOS.
Короче говоря, при работе над новым табличным представлением мы будем исходить из этого:
К этому:
Наш план в статье таков:
- Создайте повторно используемую библиотеку для работы с табличными представлениями.
- Создайте пример приложения на его основе.
Исходный код готовой библиотеки и примера приложения доступны внизу статьи. Без дальнейших церемоний, давайте начнем.
Давайте начнем
Во-первых, давайте определим протокол TableRowModel
:
- Свойство
identifier
будет содержать идентификатор повторного использования для конкретногоUITableViewCell
. - Закрытие
onSelection
будет вызываться, когда сработает методdidSelectRowAtIndexPath
объектаUITableView
. - Мы предоставляем значение по умолчанию
nil
для замыкания в расширении протокола, поэтому ячейки реализуют это свойство только при необходимости.
Далее, давайте сделаем то же самое для представления UITableViewHeaderFooterView
, создав TableHeaderFooterModel
:
Здесь мы указываем только identifier
в качестве требования.
Закончив представления UITableViewCell
и UITableHeaderFooterView
, давайте теперь смоделируем часть табличного представления:
- Так же, как любой раздел в
UITableView
может иметьUITableViewHeaderFooter
, здесь у нас есть свойствоTableHeaderFooterModel
, которое мы создали ранее. - Точно так же у нас есть массив строк, каждая из которых представляет
UITableViewCell
. - Наконец, мы делаем то же самое для нижних колонтитулов разделов.
- Инициализатор установит значение
nil
для верхних и нижних колонтитулов по умолчанию.
Теперь, когда у нас есть все базовые представления, давайте создадим базовый класс UITableViewCell
, от которого будут наследоваться все конкретные ячейки в наших приложениях:
- Мы реализуем инициализатор, который устанавливает
selectionStyle
в.none
и вызывает методsetupSubviews()
. - Мы определяем метод
setupSubviews()
, который необходимо переопределить в подклассах. Когда подкласс реализует этот метод, он будет автоматически вызван в инициализаторе, что уменьшит объем работы, которую мы должны выполнить. - То же самое делаем с методом
setup(with rowModel: TableRowModel)
. Когда мы хотим настроить ячейку с данными, мы будем вызывать этот метод за пределами конкретной ячейки.
Давайте закончим настройку базовых классов, реализовав BaseHeaderFooterView
:
Как и в BaseTableViewCell
, здесь у нас есть методы setupSubviews()
и setup(with headerFooterModel)
для переопределения подклассов.
Осталось сделать только одну вещь, чтобы закончить библиотеку: фактический объект, отвечающий за UITableView
— TableDirector
.
Реализация TableDirector
Во-первых, давайте создадим класс TableDirector
с определенным в нем свойством UITableView
:
- Мы сохраняем слабую ссылку на
UITableView
, так как нет необходимости в сильной ссылке, чтобы сохранить его живым: конкретныйUIViewController
сохранитUITableView
. - Мы устанавливаем для свойств
dataSource
иdelegate
tableview
значениеself
.
Теперь нам нужно реализовать UITableViewDelegate
и UITableViewDataSource
, чтобы избежать их реализации в будущем:
Прежде чем мы перейдем к этим методам с содержимым, давайте создадим несколько свойств вместе со вспомогательной функцией для регистрации ячеек:
- Свойство
sections
будет содержать массивTableSection
моделей. Как только свойство установлено, мы регистрируем всеUITableViewCell
иUITableViewHeaderFooterView
, используя свойствоidentifier
TableRowModel
иTableHeaderFooterModel
. - Мы добавляем замыкания
onCellSelection
иonCellWillDisplay
, чтобы потребители (особенно контроллеры представлений) могли реагировать на жизненный цикл ячейки и события, когда им это нужно. - Вспомогательный метод
classFromString
используется внутриdidSet
свойстваsections
для получения конкретного идентификатора повторного использованияUITableViewCell
илиUITableViewHeaderFooterView
.
Теперь, когда все свойства установлены, давайте наполним эти методы UITableViewDataSource
и UITableViewDelegate
реальным содержимым.
Сначала начнем с методов источника данных:
- Как бы просто это ни было, в методах
numberOfSections
мы возвращаемcount
свойстваsections
. - Точно так же в методе
numberOfRowsInSection
мы возвращаемcount
изrowModels
в конкретномsection
. - В методе
cellForRowAt
мы получаем соответствующийrowModel
текущемуIndexPath
и удаляем из очередиBaseTableViewCell
. Затем мы вызываем методsetup(with rowModel)
для ячейки и возвращаем его.
Создав источник данных, давайте теперь займемся методами делегата:
- В методе
didSelectRow
мы вызываем замыканиеonCellSelection
TableDirector
в дополнение к закрытиюonSelection
конкретногоTableRowModel
. У нас есть две версии для гибкости, поскольку иногда потребителю API будет интересен только выбранныйIndexPath
, а не фактическая модель. Имея обе версии, у нас есть более широкий спектр возможностей. - В методе
willDisplayCell
мы запускаем замыканиеonCellWillDisplay
. - В методе
viewForHeader
мы пытаемся получитьheaderModel
для определенногоIndexPath
. Если он существует, мы удаляем конкретныйBaseTableHeaderView
из очереди, вызываем для него методsetup(with headerModel)
и возвращаем его. В противном случае мы возвращаемnil
. - В методе
viewForFooter
мы делаем те же шаги, что и внутри методаviewForHeader
. - В методе
willDisplayHeaderView
мы устанавливаем визуальную конфигурацию для заголовка. - Метод
heightForHeader
использует значениеUITableView.automaticDimension
, если дляIndexPath
существует модель заголовка. В противном случае используется высота1
. - Аналогичная логика происходит внутри метода
heightForFooter
.
Наконец-то мы закончили реализацию библиотеки. Теперь пришло время использовать его в реальном приложении!
Пример приложения
Наше приложение представляет собой одноэкранное приложение, которое показывает UITableView
, заполняющее доступное пространство:
Мы хотим заполнить табличное представление данными, поэтому давайте сначала создадим конкретные TableRowModel
s.
Начнем с ячейки, показывающей метки слева и справа от нее:
- Мы указываем, что модель должна быть
reuseIdentifier
изLeftRightTitlesTableViewCell
. - Мы определяем свойства
leftTitle
,rightTitle
, которые будут отображаться вleftLabel
иrightLabel
соответственно. Кроме того, мы добавляем свойствоonSelection
TableRowModel
, так как мы будем реагировать на нажатия именно на эту ячейку. - Внутри
LeftRightTitlesTableViewCell
мы присваиваем значения моделиleftLabel
иrightLabel
внутриsetup(with rowModel)
метода. - Метод
setupSubviews()
отвечает только за макет и ограничения.
Создав один тип ячейки, давайте создадим другой:
- Модель получает указание использовать
reuseIdentifier
TitleSwitchTableViewCell
. - Добавлены свойства
title
,isOn
иonSwitchValueChanged
, чтобы установить заголовок для метки, начальное состояниеUISwitch
и реагировать на изменения значения переключателя. - В методе
setup(with rowModel)
мы настраиваем ячейку так же, как и раньше. - Метод
stateSwitchHandler
срабатывает при изменении значения переключателя и вызываетonSwitchValueChanged
закрытие модели.] - Метод
setupSubviews
размещает подпредставления и устанавливает ограничения.
Прежде чем мы начнем интегрировать ячейки в UITableView
, давайте для полноты картины также реализуем UITableViewHeaderFooterView
:
- Модель будет использовать
reuseIdentifier
TitleHeaderView
. - В методе
setup(with headerModel)
мы настраиваем метку с текстом. - Метод
setupSubviews()
размещает метку и устанавливает ограничения.
Большой! Ячейки и заголовок готовы. Теперь мы собираемся интегрировать их в UITableView
:
- Мы инициализируем отложенное свойство
TableDirector
в файлеViewController
. - Добавьте свойство
isSwitchOn
для хранения текущего значения переключателя в реализуемомTitleSwitchTableViewCell
.
Единственный шаг для того, чтобы табличное представление заработало, — это передать TableDirector
разделов. Давайте сделаем именно это:
- Мы вызываем метод
setupSections()
(который мы сейчас создадим) вviewDidLoad()
. - Метод
setupSections()
устанавливает разделы для файлаtableDirector
. - Метод
createFirstSection()
создает раздел с заголовком и тремяLeftRightTitlesTableViewCell
. - Чтобы сделать создание
LeftRightTitlesTableViewCell
более быстрым и удобным для повторного использования, используется методcreateLeftRightCellModel(leftTitle:rightTitle:)
. Он реализует базовое замыканиеonSelection
, которое будет печататьleftTitle
иrightTitle
каждый раз при выборе ячейки. - Метод
createSecondSection()
создает еще один раздел с заголовком и одинTitleSwitchTableViewCell
. Он также реализует замыканиеonSwitchValueChanged
, которое обновляет свойствоisSwitchOn
объектаViewController
.
Наконец, если мы соберем и запустим, мы увидим макет:
Отныне, чтобы создать представление таблицы с ячейками, нам нужно будет использовать только эти шаги:
- Создайте бетон
TableRowModel
. - Создайте бетон
BaseTableViewCell
. - Добавьте свойство
TableDirector
кViewController
и назначьте ему табличное представление. - Установите свойство разделов
TableDirector
.
Ресурсы
Исходный код примера приложения доступен на GitHub. Кроме того, я подготовил готовый к использованию пакет Swift, доступ к которому вы можете получить здесь.
Я надеюсь, что вы нашли этот урок полезным, и теперь вам потребуется меньше времени для работы с UITableView
s и UITableViewCell
s.
Спасибо за прочтение!