
Introdução
UITableView é um dos componentes UIKit mais usados . A visualização tabular se estabeleceu como uma das mais convenientes interações do usuário com o conteúdo apresentado na tela do smartphone.
Hoje, todo desenvolvedor iOS precisa ser fluente em UITableView, conhecer os meandros e entender como adaptá-lo às diferentes arquiteturas para que seu uso não cause problemas e dificuldades desnecessários.
, 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.
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. , , . .
Todo o código apresentado neste artigo pode ser baixado deste link.