I\'m stuck on a design decision with creating view-models for table view\'s cells. Data for each cell is provided by a data source class (has an array of Contacts
)
Let me start with some theory. MVVM is a specialization of the Presentation Model (or Application Model) for Microsoft's Silverlight and WPF. The main ideas behind this UI architectural pattern are:
The benefits are as you mentioned:
So coming back to your question, the implementation of the UITableViewDataSource
protocol belongs to the view part of the architecture, because of its dependencies on the UI framework. Notice that in order to use that protocol in your code, that file must import UIKit. Also methods like tableView(:cellForRowAt:)
that returns a view is heavily dependent on UIKit.
Then, your array of Contacts
, that is indeed your model, cannot be operated or queried through the view (data source or otherwise). Instead you pass a view model to your table view controller, that, in the simplest case, has two properties (I recommend that they are stored, not computed properties). One of them is the number of sections and the other one is the number of rows per section:
var numberOfSections: Int = 0
var rowsPerSection: [Int] = []
The view model is initialized with a reference to the model and as the last step in the initialization it sets the value of those two properties.
The data source in the view controller uses the data of the view model:
override func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.numberOfSections
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.rowsPerSection[section]
}
Finally you can have a different view model struct for each of the cells:
struct ContactCellViewModel {
let name: String
init(contact: Contact) {
name = contact.name ?? ""
}
}
And the UITableViewCell
subclass will know how to use that struct:
class ContactTableViewCell: UITableViewCell {
var viewModel: ContactCellViewModel!
func configure() {
textLabel!.text = viewModel.name
}
}
In order to have the corresponding view model for each of the cells, the table view view model will provide a method that generates them, and that can be used to populate the array of view models:
func viewModelForCell(at index: Int) -> ContactCellViewModel {
return ContactCellViewModel(contact: contacts[index])
}
As you can see the view models here are the only ones talking to the model (your Contacts
array), and the views only talk to the view models.
Hope this helps.