Мотивы

Часто в приложениях встречаются табличные представления UITableView, UICollectionView. В зависимости от задачи я использовал в качестве структуры данных массив либо словарь. Это решение несло ряд ограничений и не было гибким. Для меня более удобным стало использование компоновщика. Проект с примером на Git-Hub :octocat:

Компоновщик — структурный паттерн проектирования. Он позволяет сгруппировать объекты в древовидную структуру, а затем работать с ними как с единым объектом.


Теория

Как будет выглядеть наше дерево? Табличное представление состоит из списка секций. В свою очередь, секции содержат модели ячеек (тоже самое для коллекций). Таким образом нам нужны элементы двух типов которые могут содержать дочерние элементы (контейнеры) и конечные элементы (листья) в которые нельзя добавлять детей.

Контейнер (Композит) — составной элемент дерева. Содержит дочерние элементы — Листья или другие Контейнеры — но не знает, какие именно, так как работает с ними только через общий интерфейс Компонента (CompoundItemProtocol). Методы этого класса переадресуют основную работу своим дочерним компонентам, хотя и могут добавлять что-то своё к результату. Таким образом, паттерн Компоновщик позволяет нам работать с деревьями объектов любой сложности, не обращая внимание на конкретные модели, формирующие дерево.


Реализация

Напишем общий интерфейс для элементов дерева CompoundItemProtocol, все элементы нашей структуры должны релиазовать этот протокол. Так же создадим перечисление с доступными уровнями вложенности CompoundItemLevel.

Компонент определяет общий интерфейс для простых и составных компонентов дерева.


Так как все модели должны его реализовывать - напишем базовый каласс BaseCompoundItem на базе которого будем добавлять необходимые модели нашего дерева:

Контейнер (или Композит) — это составной элемент дерева.


Базовая реализация готова. К ней можно добавить конкретные модели: секций и ячееки. Для создания модели элементов дерева будем использовать паттерн декоратор, чтобы расширить нашу базовую реализацию BaseCompoundItem. Этот структурный паттерн позволит динамически добавить объектам новую функциональность, используя так называемые обвертки.

Напишем модель для секции:

Модель секции тоже является контейнером так как может содержать дочернии элементы.


И модель для ячейки:

Модель ячейки является листом – это простой элемент дерева, не имеющий ответвлений.


Использование

Код написан, можно опробовать нашу новую структуру данных:

Теперь выполняем tableView.reloadData(), и готово. Посмотреть пример кода можно тут :octocat: .


В заключении

Что полезного можно добавить:

  • Фильтры в модели ячеек. Добавим дополнительный параметр isVisible — от него зависит, будут ли модели возвращаться.
// CellCompoundItem
var isVisible = false
var items: [CompoundItemProtocol] {
    return self.isVisible ? [self] : []
}
  • Проверка валидации всех полей таблицы. Добавляем новый метод func isValid() -> Bool в CompoudItemProtocol. Реализуем его во всех моделях ячеек нашего дерева.
  • Проверка изменений данных таблицы при создании сложных форм ввода данных (создается по аналогии с п. 2).

Есть вопросы? Пишите сюда – @alobanov