
introduction
UITableView est l' un des composants UIKit les plus utilisés . La vue tabulaire s'est imposée comme l'une des interactions utilisateur les plus pratiques avec le contenu présenté sur l'écran d'un smartphone.
Aujourd'hui, chaque développeur iOS doit maîtriser UITableView, connaître les subtilités et comprendre comment l'adapter à différentes architectures afin que son utilisation ne pose pas de problèmes et de difficultés inutiles.
, 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. , , . .
Tout le code présenté dans cet article peut être téléchargé à partir de ce lien.