Андрей Резанов
Построение сложного
табличного интерфейса
7.8
Название
Тип
Тег №2Тег №1
Станция метро
Адерс места
Название места
Лучшие Недавние
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud
exercitation ullamco…... далее
Имя пользователя
Дата
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud
exercitation ullamco… далее
Заголовок блока
Важная информация
Дополнительная информация
Режим работы
Расписание работы
Режим работы:
7.8
Название
Тип
Станция метро
Адерс места
Название места
Лучшие Недавние
Заголовок блока
Важная информация
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud
exercitation ullamco… далее
Дополнительная информация
Дополнительная информация
Дополнительная информация
Тег №2Тег №1
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud
exercitation ullamco…... далее
Имя пользователя
Дата
Режим работы
Расписание работы
Режим работы:
7.8
Название
Тип
Лучшие Недавние
Заголовок блока
Важная информация
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud
exercitation ullamco…... далее
Имя пользователя
Дата
Режим работы
Расписание работы
Режим работы:
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud
exercitation ullamco… далее
Станция метро
Адерс места
Название места
Дополнительная информация
Дополнительная информация
Дополнительная информация
Дополнительная информация
Тег №2Тег №1
Построение сложного табличного интерфейса
Постановка задачи – Афиша iOS
• Уметь строить 30+ различных ячеек
• Располагать ячейки в различном порядке в зависимости
от 10 + конфигураций экрана
• В будущем необходимо добавлять новый ячейки /
конфигурации экрана
Построение сложного табличного интерфейса
Решение в лоб! Why not?
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Creation *creation = [self itemAtIndexPath:indexPath];
NSString *cellIdentifier = [self cellIdentifierForObject:creation
atIndexPath:indexPath];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
if ([cellIdentifier isEqualToString:RCATitleTableViewCell]) {
TitleTableViewCell *titleCell = (TitleTableViewCell *)cell;
[titleCell fillWithCreation:creation];
} else if ([cellIdentifier isEqualToString:RCARatingTableViewCell]) {
RatingTableViewCell *ratingCell = (RatingTableViewCell *)cell;
[ratingCell fillWithRating:creation.rating];
}
...
return cell;
}
Построение сложного табличного интерфейса
• Неконтролируемый рост полотна кода в cellForRow
• Сложно добавлять новые ячейки, модифицировать
порядок ячеек
• Нарушение принципа единственной ответственности
(SOLID)
Решение в лоб - Проблемы
Построение сложного табличного интерфейса
Решение в лоб! Why not?
Декомпозиция
Построение сложного табличного интерфейса
Шаг 1. Внедряем View-модели 🐣
Модель представления - абстракция представления,
которая содержит свойства Модели.
Построение сложного табличного интерфейса
Шаг 1. Внедряем View-модели 🐣
Построение сложного табличного интерфейса
Класс TableViewModel
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
id viewModel = [self viewModelAtIndexPath:indexPath];
. . .
}
1. Получаем view-модель по indexPath
Построение сложного табличного интерфейса
Класс TableViewModel
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
Class cellClass = [self cellClassFromViewModel:viewModel];
. . .
}
2. Получаем класс ячейки у view-модели
Построение сложного табличного интерфейса
Класс TableViewModel
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
cell = [tableView
dequeueReusableCellWithIdentifier:NSStringFromClass(cellClass)];
. . .
}
3. Реюзаем / создаем ячейку
Построение сложного табличного интерфейса
Класс TableViewModel
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
[cell updateCellWithViewModel:viewModel];
. . .
}
4. Заполняем ячейку данными из view-модели
Построение сложного табличного интерфейса
Класс TableViewActions
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
id viewModel = [model viewModelAtIndexPath:indexPath];
. . .
}
1. Получаем view-модель по indexPath
Построение сложного табличного интерфейса
Класс TableViewActions
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
NSString *cellHeightID = [self cellHeightIDWithTableView:tableView
viewModel:viewModel];
NSNumber *cachedHeight = [self.cellHeightCache objectForKey:cellHeightID];
if (cachedHeight) {
return [cachedHeight floatValue];
}
. . .
}
2. Если есть закэшированное значение
высоты ячейки, то возвращаем его
Построение сложного табличного интерфейса
Класс TableViewActions
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
Class cellClass = [viewModel cellClass];
. . .
}
3. Получаем класс ячейки у view-модели
Построение сложного табличного интерфейса
Класс TableViewActions
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
CGFloat height;
if ([cellClass
respondsToSelector:@selector(heightForViewModel:atIndexPath:tableView:)]) {
height = [cellClass heightForViewModel:viewModel
atIndexPath:indexPath
tableView:tableView];
} else {
. . .
}
4. Если ячейки умеет сама рассчитывать
высоту, то возвращаем ее
Построение сложного табличного интерфейса
Класс TableViewActions
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
UITableViewCell<Cell> *cell = [self tableViewCellWithViewModel:tableView
tableView:tableView];
[cell updateCellWithViewModel:viewModel];
height = [self tableView:tableView heightForTableViewCell:cell];
. . .
}
5. Считаем высоту через статичный
прототип ячейки
Построение сложного табличного интерфейса
Класс TableViewActions
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
. . .
[cellHeightCache setObject:@(height)
forKey:cellHeightID];
. . .
}
6. Кэшируем значение высоты
Построение сложного табличного интерфейса
Чего-то не хватает?
Построение сложного табличного интерфейса
Метод построения view-моделей
2000 + строк кода
Построение сложного табличного интерфейса
Шаблон проектирования Строитель (англ. Builder)
Порождающий шаблон проектирования
предоставляет способ создания составного
объекта.
Построение сложного табличного интерфейса
Шаг 2. Внедряем Билдер
Построение сложного табличного интерфейса
Диаграмма последовательности (англ. Sequence diagram)
Построение сложного табличного интерфейса
Выводы
1. Внедрение view-моделей дает: слабую связность, дополнительный
слой между моделью и view
2. Никакой логики во view
3. Чистый и универсальный cellForRow, heightForRow
4. Внедрение билдера дает: разделение логики построения блоков ячеек
от логики сбора их вместе
5. Легкая возможность расширения данной схемы. Как появление новых
блоков/ячеек, так и появление новых экранов, например - для квестов!
6. Модульность кода
Построение сложного табличного интерфейса
Литература и ссылки
The "Gang of Four” - Design Patterns: Elements of Reusable
Object-Oriented Software
Erick Buck, Donald Yachtman - Cocoa Design Patterns
OBJC.IO Issue 13 - Architecture
OODesign.com - Builder pattern
NimbusKit - Table View Models
Построение сложного табличного интерфейса
Спасибо!

Rambler.iOS #7: Построение сложного табличного интерфейса

  • 1.
  • 2.
    7.8 Название Тип Тег №2Тег №1 Станцияметро Адерс места Название места Лучшие Недавние Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco…... далее Имя пользователя Дата Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco… далее Заголовок блока Важная информация Дополнительная информация Режим работы Расписание работы Режим работы:
  • 3.
    7.8 Название Тип Станция метро Адерс места Названиеместа Лучшие Недавние Заголовок блока Важная информация Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco… далее Дополнительная информация Дополнительная информация Дополнительная информация Тег №2Тег №1 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco…... далее Имя пользователя Дата Режим работы Расписание работы Режим работы:
  • 4.
    7.8 Название Тип Лучшие Недавние Заголовок блока Важнаяинформация Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco…... далее Имя пользователя Дата Режим работы Расписание работы Режим работы: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco… далее Станция метро Адерс места Название места Дополнительная информация Дополнительная информация Дополнительная информация Дополнительная информация Тег №2Тег №1
  • 5.
    Построение сложного табличногоинтерфейса Постановка задачи – Афиша iOS • Уметь строить 30+ различных ячеек • Располагать ячейки в различном порядке в зависимости от 10 + конфигураций экрана • В будущем необходимо добавлять новый ячейки / конфигурации экрана
  • 6.
    Построение сложного табличногоинтерфейса Решение в лоб! Why not? - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { Creation *creation = [self itemAtIndexPath:indexPath]; NSString *cellIdentifier = [self cellIdentifierForObject:creation atIndexPath:indexPath]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath]; if ([cellIdentifier isEqualToString:RCATitleTableViewCell]) { TitleTableViewCell *titleCell = (TitleTableViewCell *)cell; [titleCell fillWithCreation:creation]; } else if ([cellIdentifier isEqualToString:RCARatingTableViewCell]) { RatingTableViewCell *ratingCell = (RatingTableViewCell *)cell; [ratingCell fillWithRating:creation.rating]; } ... return cell; }
  • 7.
    Построение сложного табличногоинтерфейса • Неконтролируемый рост полотна кода в cellForRow • Сложно добавлять новые ячейки, модифицировать порядок ячеек • Нарушение принципа единственной ответственности (SOLID) Решение в лоб - Проблемы
  • 8.
    Построение сложного табличногоинтерфейса Решение в лоб! Why not? Декомпозиция
  • 9.
    Построение сложного табличногоинтерфейса Шаг 1. Внедряем View-модели 🐣 Модель представления - абстракция представления, которая содержит свойства Модели.
  • 10.
    Построение сложного табличногоинтерфейса Шаг 1. Внедряем View-модели 🐣
  • 11.
    Построение сложного табличногоинтерфейса Класс TableViewModel - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { . . . id viewModel = [self viewModelAtIndexPath:indexPath]; . . . } 1. Получаем view-модель по indexPath
  • 12.
    Построение сложного табличногоинтерфейса Класс TableViewModel - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { . . . Class cellClass = [self cellClassFromViewModel:viewModel]; . . . } 2. Получаем класс ячейки у view-модели
  • 13.
    Построение сложного табличногоинтерфейса Класс TableViewModel - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { . . . cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(cellClass)]; . . . } 3. Реюзаем / создаем ячейку
  • 14.
    Построение сложного табличногоинтерфейса Класс TableViewModel - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { . . . [cell updateCellWithViewModel:viewModel]; . . . } 4. Заполняем ячейку данными из view-модели
  • 15.
    Построение сложного табличногоинтерфейса Класс TableViewActions - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { . . . id viewModel = [model viewModelAtIndexPath:indexPath]; . . . } 1. Получаем view-модель по indexPath
  • 16.
    Построение сложного табличногоинтерфейса Класс TableViewActions - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { . . . NSString *cellHeightID = [self cellHeightIDWithTableView:tableView viewModel:viewModel]; NSNumber *cachedHeight = [self.cellHeightCache objectForKey:cellHeightID]; if (cachedHeight) { return [cachedHeight floatValue]; } . . . } 2. Если есть закэшированное значение высоты ячейки, то возвращаем его
  • 17.
    Построение сложного табличногоинтерфейса Класс TableViewActions - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { . . . Class cellClass = [viewModel cellClass]; . . . } 3. Получаем класс ячейки у view-модели
  • 18.
    Построение сложного табличногоинтерфейса Класс TableViewActions - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { . . . CGFloat height; if ([cellClass respondsToSelector:@selector(heightForViewModel:atIndexPath:tableView:)]) { height = [cellClass heightForViewModel:viewModel atIndexPath:indexPath tableView:tableView]; } else { . . . } 4. Если ячейки умеет сама рассчитывать высоту, то возвращаем ее
  • 19.
    Построение сложного табличногоинтерфейса Класс TableViewActions - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { . . . UITableViewCell<Cell> *cell = [self tableViewCellWithViewModel:tableView tableView:tableView]; [cell updateCellWithViewModel:viewModel]; height = [self tableView:tableView heightForTableViewCell:cell]; . . . } 5. Считаем высоту через статичный прототип ячейки
  • 20.
    Построение сложного табличногоинтерфейса Класс TableViewActions - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { . . . [cellHeightCache setObject:@(height) forKey:cellHeightID]; . . . } 6. Кэшируем значение высоты
  • 21.
    Построение сложного табличногоинтерфейса Чего-то не хватает?
  • 22.
    Построение сложного табличногоинтерфейса Метод построения view-моделей 2000 + строк кода
  • 23.
    Построение сложного табличногоинтерфейса Шаблон проектирования Строитель (англ. Builder) Порождающий шаблон проектирования предоставляет способ создания составного объекта.
  • 24.
    Построение сложного табличногоинтерфейса Шаг 2. Внедряем Билдер
  • 25.
    Построение сложного табличногоинтерфейса Диаграмма последовательности (англ. Sequence diagram)
  • 26.
    Построение сложного табличногоинтерфейса Выводы 1. Внедрение view-моделей дает: слабую связность, дополнительный слой между моделью и view 2. Никакой логики во view 3. Чистый и универсальный cellForRow, heightForRow 4. Внедрение билдера дает: разделение логики построения блоков ячеек от логики сбора их вместе 5. Легкая возможность расширения данной схемы. Как появление новых блоков/ячеек, так и появление новых экранов, например - для квестов! 6. Модульность кода
  • 27.
    Построение сложного табличногоинтерфейса Литература и ссылки The "Gang of Four” - Design Patterns: Elements of Reusable Object-Oriented Software Erick Buck, Donald Yachtman - Cocoa Design Patterns OBJC.IO Issue 13 - Architecture OODesign.com - Builder pattern NimbusKit - Table View Models
  • 28.