Adaptando UITableView para MVVM

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.








All Articles