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
)
Unless you have a specific problem that's solved with Model-View-ViewModel
then attempting to adopt it just for 'best practices' is going to end up introducing a lot of unnecessary complexity.
Your data-source is what's responsible for populating your table. Nothing other than your datasource needs a reference to contacts
as it will update your table with this data.
View Models
only come into play when you need to do complex UI interactions and updates. The VM is responsible for encapsulating the state of your view, things like...
When changes are made to your View, your View Model
is responsible for making updates to your Model
(when necessary) in order to reflect the changes that have been made to that Model
through the UI.
With all that said, View Models don't make sense in IOS that's because IOS makes use of View Controllers
in the design methodology called MVC
(Model-View-Controller)
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.