[Разработка под iOS, Разработка мобильных приложений] Адаптируем UITableView под MVVM
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
ВведениеUITableView один из самых часто используемых компонентов UIKit. Табличное представление зарекомендовало себя как одно из самых удобных взаимодействий пользователя с контентом представленным на экране смартфона.На сегодняшний день, каждому iOS разработчику необходимо в совершенстве владеть UITableView, знать тонкости и понимать как его адаптировать под разные архитектуры, чтобы использование не вызывало лишних проблем и трудностей.В этой статье мы поговорим о том, как адаптировать UITableView под архитектуру Model-View-ViewModel (MVVM). Начнём.Содержание
- Введение
- Пример
- Реализация
- Использование
- Результат
- Вывод
ПримерВ качестве примера я реализовал ячейку с кнопкой, картинкой и текстом.
РеализацияПервым делом создадим подкласс от UITableView и назовем его AdaptedTableView.
class AdaptedTableView: UITableView {
}
Определим метод setup(). Он необходим для конфигурации таблицы. Временно заполним обязательные для реализации методы UITableViewDataSource.
class AdaptedTableView: UITableView {
// MARK: - Public methods
func setup() {
self.dataSource = self
}
}
// MARK: - UITableViewDataSource
extension AdaptedTableView: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
.zero
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
.zero
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
UITableViewCell()
}
}
Согласно паттерну MVVM, view владеет viewModel. Создадим абстракцию для входных данных и назовем её AdaptedViewModelInputProtocol. AdaptedSectionViewModelProtocol необходим для описания viewModel секции. AdaptedCellViewModelProtocol служит лишь для полиморфизма подтипов наших viewModels для ячеек.
protocol AdaptedCellViewModelProtocol { }
protocol AdaptedSectionViewModelProtocol {
var cells: [AdaptedCellViewModelProtocol] { get }
}
protocol AdaptedViewModelInputProtocol {
var sections: [AdaptedSectionViewModelProtocol] { get }
}
Добавляем viewModel. Теперь у нас есть возможность корректно заполнить методы UITableViewDataSource.
class AdaptedTableView: UITableView {
// MARK: - Public properties
var viewModel: AdaptedViewModelInputProtocol?
// MARK: - Public methods
func setup() {
self.dataSource = self
}
}
// MARK: - UITableViewDataSource
extension AdaptedTableView: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
viewModel?.sections.count ?? .zero
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel?.sections[section].cells.count ?? .zero
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cellViewModel = viewModel?.sections[indexPath.section].cells[indexPath.row] else {
return UITableViewCell()
}
// TO DO: - Register cell
// TO DO: - Create cell
return UITableViewCell()
}
}
На данном этапе с AdaptedTableView почти все готов, однако есть еще пару нерешенных вопросов. Регистрация и переиспользование ячеек. Создадим протокол AdaptedCellProtocol, который будут реализовывать все наши подклассы UITableViewCell, добавим метод register(_ tableView:) и reuse(_ tableView:, for indexPath:).
protocol AdaptedCellProtocol {
static var identifier: String { get }
static var nib: UINib { get }
static func register(_ tableView: UITableView)
static func reuse(_ tableView: UITableView, for indexPath: IndexPath) -> Self
}
extension AdaptedCellProtocol {
static var identifier: String {
String(describing: self)
}
static var nib: UINib {
UINib(nibName: identifier, bundle: nil)
}
static func register(_ tableView: UITableView) {
tableView.register(nib, forCellReuseIdentifier: identifier)
}
static func reuse(_ tableView: UITableView, for indexPath: IndexPath) -> Self {
tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! Self
}
}
Для порождения ячеек создадим протокол фабричного метода AdaptedCellFactoryProtocol.
protocol AdaptedCellFactoryProtocol {
var cellTypes: [AdaptedCellProtocol.Type] { get }
func generateCell(viewModel: AdaptedCellViewModelProtocol, tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell
}
Добавим поле cellFactory и в didSet поместим регистрацию всех ячеек.
class AdaptedTableView: UITableView {
// MARK: - Public properties
var viewModel: AdaptedViewModelInputProtocol?
var cellFactory: AdaptedCellFactoryProtocol? {
didSet {
cellFactory?.cellTypes.forEach({ $0.register(self)})
}
}
...
}
Исправим метод делегата.
extension AdaptedTableView: UITableViewDataSource {
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cellFactory = cellFactory,
let cellViewModel = viewModel?.sections[indexPath.section].cells[indexPath.row]
else {
return UITableViewCell()
}
return cellFactory.generateCell(viewModel: cellViewModel, tableView: tableView, for: indexPath)
}
}
ИспользованиеС необходимы абстракциями на этом все, пора перейти к конкретным реализациям.1. ЯчейкаВ качестве примера я создам ячейку с лейблом по центру и viewModel к ней. Реализация ячейки с кнопкой и картинкой.
protocol TextCellViewModelInputProtocol {
var text: String { get }
}
typealias TextCellViewModelType = AdaptedCellViewModelProtocol & TextCellViewModelInputProtocol
class TextCellViewModel: TextCellViewModelType {
var text: String
init(text: String) {
self.text = text
}
}
final class TextTableViewCell: UITableViewCell, AdaptedCellProtocol {
// MARK: - IBOutlets
@IBOutlet private weak var label: UILabel!
// MARK: - Public properties
var viewModel: TextCellViewModelInputProtocol? {
didSet {
bindViewModel()
}
}
// MARK: - Private methods
private func bindViewModel() {
label.text = viewModel?.text
}
}
2. Cекция
class AdaptedSectionViewModel: AdaptedSectionViewModelProtocol {
// MARK: - Public properties
var cells: [AdaptedCellViewModelProtocol]
// MARK: - Init
init(cells: [AdaptedCellViewModelProtocol]) {
self.cells = cells
}
}
3. Фабрика
struct MainCellFactory: AdaptedSectionFactoryProtocol {
var cellTypes: [AdaptedCellProtocol.Type] = [
TextTableViewCell.self
]
func generateCell(viewModel: AdaptedCellViewModelProtocol, tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell {
switch viewModel {
case let viewModel as TextCellViewModelType:
let view = TextTableViewCell.reuse(tableView, for: indexPath)
view.viewModel = viewModel
return view
default:
return UITableViewCell()
}
}
}
Ну и напоследок нам понадобится viewModel самого модуля.
final class MainViewModel: AdaptedSectionViewModelType {
// MARK: - Public properties
var sections: [AdaptedSectionViewModelProtocol]
// MARK: - Init
init() {
self.sections = []
self.setupMainSection()
}
// MARK: - Private methods
private func setupMainSection() {
let section = AdaptedSectionViewModel(cells: [
TextCellViewModel(text: "Hello!"),
TextCellViewModel(text: "It's UITableView with using MVVM")
])
sections.append(section)
}
}
Все готово, пора добавить UITableView на ViewController, установив в качестве custom class наш AdaptedTableView.
В реальном проекте, MVVM очень часто используют с каким-то паттерном навигации, это может быть координатор или роутер. В зону ответственности таких объектов входит DI (Dependency Injection) внедрение всех необходимых модулю зависимостей. Так как это тестовый проект, я захардкодил viewModel и cellFactory прямо во ViewController.
class ViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var tableView: AdaptedTableView! {
didSet {
tableView.viewModel = MainViewModel()
tableView.cellFactory = MainCellFactory()
tableView.setup()
}
}
}
Результат
ВыводВ итоге мы получили решение, которое позволяет удобно использовать UITableView с MVVM. Стало очень просто работать с секциями, настраивать ячейки, писать меньше шаблонного кода. В то же время осталась возможность настройки таблицы и расширения функционала при необходимости.Весь код представленный в этой статье можно скачать по этой ссылке.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка под iOS, Смартфоны, Сотовая связь] Пользователи iPhone 12 жалуются, что у них пропадает связь
- [Программирование, Разработка под iOS, Objective C, Swift] Модуляризация iOS-приложения: зачем и как мы разбиваем Badoo на модули
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android] Как ВТБ помогает снизить комиссию за приём платежей до 0,4% с помощью QR-кода
- [Разработка под iOS, ООП] Зачем нужно понимать ООП
- [Разработка под iOS, Разработка мобильных приложений, Swift] Как мы стартовали Vivid Money для iOS
- [Разработка под iOS, Исследования и прогнозы в IT, Аналитика мобильных приложений] Apple рассказала о самых популярных приложениях 2020 года в App Store
- [Информационная безопасность, Разработка под iOS, Смартфоны] Эксплойт памяти ядра в iPhone позволял красть данные без участия пользователя по Wi-Fi
- [Разработка мобильных приложений, Дизайн мобильных приложений] Новое лицо для «Честного знака»: работы победителей Znak Cup
- [Разработка под iOS, Amazon Web Services, Разработка под MacOS, DevOps] AWS анонсировал возможность запускать MacOS инстансы
- [Разработка под iOS, Законодательство в IT] Apple не согласилась изменить iOS по требованию «Лаборатории Касперского»
Теги для поиска: #_razrabotka_pod_ios (Разработка под iOS), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_swift, #_uikit, #_mvvm, #_patterns, #_ios_development, #_mobile_development, #_razrabotka_pod_ios (
Разработка под iOS
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:46
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
ВведениеUITableView один из самых часто используемых компонентов UIKit. Табличное представление зарекомендовало себя как одно из самых удобных взаимодействий пользователя с контентом представленным на экране смартфона.На сегодняшний день, каждому iOS разработчику необходимо в совершенстве владеть UITableView, знать тонкости и понимать как его адаптировать под разные архитектуры, чтобы использование не вызывало лишних проблем и трудностей.В этой статье мы поговорим о том, как адаптировать UITableView под архитектуру Model-View-ViewModel (MVVM). Начнём.Содержание
РеализацияПервым делом создадим подкласс от UITableView и назовем его AdaptedTableView. class AdaptedTableView: UITableView {
} class AdaptedTableView: UITableView {
// MARK: - Public methods func setup() { self.dataSource = self } } // MARK: - UITableViewDataSource extension AdaptedTableView: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { .zero } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { .zero } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { UITableViewCell() } } protocol AdaptedCellViewModelProtocol { }
protocol AdaptedSectionViewModelProtocol { var cells: [AdaptedCellViewModelProtocol] { get } } protocol AdaptedViewModelInputProtocol { var sections: [AdaptedSectionViewModelProtocol] { get } } class AdaptedTableView: UITableView {
// MARK: - Public properties var viewModel: AdaptedViewModelInputProtocol? // MARK: - Public methods func setup() { self.dataSource = self } } // MARK: - UITableViewDataSource extension AdaptedTableView: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { viewModel?.sections.count ?? .zero } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { viewModel?.sections[section].cells.count ?? .zero } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cellViewModel = viewModel?.sections[indexPath.section].cells[indexPath.row] else { return UITableViewCell() } // TO DO: - Register cell // TO DO: - Create cell return UITableViewCell() } } protocol AdaptedCellProtocol {
static var identifier: String { get } static var nib: UINib { get } static func register(_ tableView: UITableView) static func reuse(_ tableView: UITableView, for indexPath: IndexPath) -> Self } extension AdaptedCellProtocol { static var identifier: String { String(describing: self) } static var nib: UINib { UINib(nibName: identifier, bundle: nil) } static func register(_ tableView: UITableView) { tableView.register(nib, forCellReuseIdentifier: identifier) } static func reuse(_ tableView: UITableView, for indexPath: IndexPath) -> Self { tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! Self } } protocol AdaptedCellFactoryProtocol {
var cellTypes: [AdaptedCellProtocol.Type] { get } func generateCell(viewModel: AdaptedCellViewModelProtocol, tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell } class AdaptedTableView: UITableView {
// MARK: - Public properties var viewModel: AdaptedViewModelInputProtocol? var cellFactory: AdaptedCellFactoryProtocol? { didSet { cellFactory?.cellTypes.forEach({ $0.register(self)}) } } ... } extension AdaptedTableView: UITableViewDataSource {
... func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cellFactory = cellFactory, let cellViewModel = viewModel?.sections[indexPath.section].cells[indexPath.row] else { return UITableViewCell() } return cellFactory.generateCell(viewModel: cellViewModel, tableView: tableView, for: indexPath) } } protocol TextCellViewModelInputProtocol {
var text: String { get } } typealias TextCellViewModelType = AdaptedCellViewModelProtocol & TextCellViewModelInputProtocol class TextCellViewModel: TextCellViewModelType { var text: String init(text: String) { self.text = text } } final class TextTableViewCell: UITableViewCell, AdaptedCellProtocol { // MARK: - IBOutlets @IBOutlet private weak var label: UILabel! // MARK: - Public properties var viewModel: TextCellViewModelInputProtocol? { didSet { bindViewModel() } } // MARK: - Private methods private func bindViewModel() { label.text = viewModel?.text } } class AdaptedSectionViewModel: AdaptedSectionViewModelProtocol {
// MARK: - Public properties var cells: [AdaptedCellViewModelProtocol] // MARK: - Init init(cells: [AdaptedCellViewModelProtocol]) { self.cells = cells } } struct MainCellFactory: AdaptedSectionFactoryProtocol {
var cellTypes: [AdaptedCellProtocol.Type] = [ TextTableViewCell.self ] func generateCell(viewModel: AdaptedCellViewModelProtocol, tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { switch viewModel { case let viewModel as TextCellViewModelType: let view = TextTableViewCell.reuse(tableView, for: indexPath) view.viewModel = viewModel return view default: return UITableViewCell() } } } final class MainViewModel: AdaptedSectionViewModelType {
// MARK: - Public properties var sections: [AdaptedSectionViewModelProtocol] // MARK: - Init init() { self.sections = [] self.setupMainSection() } // MARK: - Private methods private func setupMainSection() { let section = AdaptedSectionViewModel(cells: [ TextCellViewModel(text: "Hello!"), TextCellViewModel(text: "It's UITableView with using MVVM") ]) sections.append(section) } } В реальном проекте, MVVM очень часто используют с каким-то паттерном навигации, это может быть координатор или роутер. В зону ответственности таких объектов входит DI (Dependency Injection) внедрение всех необходимых модулю зависимостей. Так как это тестовый проект, я захардкодил viewModel и cellFactory прямо во ViewController. class ViewController: UIViewController {
// MARK: - IBOutlets @IBOutlet weak var tableView: AdaptedTableView! { didSet { tableView.viewModel = MainViewModel() tableView.cellFactory = MainCellFactory() tableView.setup() } } } ВыводВ итоге мы получили решение, которое позволяет удобно использовать UITableView с MVVM. Стало очень просто работать с секциями, настраивать ячейки, писать меньше шаблонного кода. В то же время осталась возможность настройки таблицы и расширения функционала при необходимости.Весь код представленный в этой статье можно скачать по этой ссылке. =========== Источник: habr.com =========== Похожие новости:
Разработка под iOS ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:46
Часовой пояс: UTC + 5