Introducción
UITableView es uno de los componentes de UIKit más utilizados . La vista tabular se ha establecido como una de las interacciones de usuario más convenientes con el contenido que se muestra en la pantalla de un teléfono inteligente.
Hoy en día, todo desarrollador de iOS necesita dominar UITableView, conocer las complejidades y entender cómo adaptarlo a diferentes arquitecturas para que su uso no cause problemas y dificultades innecesarias.
, UITableView Model-View-ViewModel (MVVM). .
, .
, AdaptedTableView UITableView.
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.
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. , , . .
Todo el código presentado en este artículo se puede descargar desde este enlace.