Adapting UITableView for MVVM

Introduction

UITableView is one of the most used UIKit components . Tabular view has established itself as one of the most convenient user interactions with content displayed on a smartphone screen.





Today, every iOS developer needs to be fluent in UITableView, know the intricacies and understand how to adapt it to different architectures so that its use does not cause unnecessary problems and difficulties.





, 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. , , . .






All the code presented in this article can be downloaded from this link.








All Articles