In the previous episode, I mentioned that we are repeating ourselves in the tableView(_:cellForRowAt:) method of the SettingsViewController class. We can resolve this problem with protocol-oriented programming.
The idea is simple. The view models for each of the sections of the table view are very similar and the computed properties the view controller accesses have the same name. We can create a protocol with an interface identical to those of the view models. Let me show you what that looks like.
Creating the Protocol
Create a group in the Settings View Controller group and name it Protocols. I prefer to keep protocols close to where they are used. That is just a personal preference, though.

Create a Swift file and name it SettingsPresentable.swift.

Replace the import statement for Foundation with an import statement for UIKit and define the SettingsPresentable protocol.
SettingsPresentable.swift
import UIKit
protocol SettingsPresentable {
}
The protocol defines two properties, text of type String and accessoryType of type UITableViewCell.AccessoryType.
SettingsPresentable.swift
import UIKit
protocol SettingsPresentable {
// MARK: - Properties
var text: String { get }
// MARK: -
var accessoryType: UITableViewCell.AccessoryType { get }
}
Conforming to the Protocol
The next step is surprisingly simple. Because the three view models implicitly conform to the SettingsPresentable protocol, we only need to make their conformance to the protocol explicit. We use an extension to take care of that step.
Open SettingsTimeViewModel.swift and define an extension at the bottom. We use the extension to conform the SettingsTimeViewModel struct to the SettingsPresentable protocol.
SettingsTimeViewModel.swift
import UIKit
struct SettingsTimeViewModel {
...
}
extension SettingsTimeViewModel: SettingsPresentable {
}
We repeat these steps for the SettingsUnitsViewModel and SettingsTemperatureViewModel structs.
SettingsUnitsViewModel.swift
import UIKit
struct SettingsUnitsViewModel {
...
}
extension SettingsUnitsViewModel: SettingsPresentable {
}
SettingsTemperatureViewModel.swift
import UIKit
struct SettingsTemperatureViewModel {
...
}
extension SettingsTemperatureViewModel: SettingsPresentable {
}
We only use the extension to make the conformance of the view models to the SettingsPresentable protocol explicit. There is no need to implement the text and accessoryType properties of the SettingsPresentable protocol because the view models already implement these properties. That is important to understand.
Refactoring the Settings View Controller
It is time to update the SettingsViewController class. Open SettingsViewController.swift and navigate to the tableView(_:cellForRowAt:) method. Before entering the switch statement, we declare a variable, viewModel, of type SettingsPresentable?.
SettingsViewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let section = Section(rawValue: indexPath.section) else {
fatalError("Unexpected Section")
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.reuseIdentifier, for: indexPath) as? SettingsTableViewCell else {
fatalError("Unable to Dequeue Settings Table View Cell")
}
// Helpers
var viewModel: SettingsPresentable?
switch section {
...
}
return cell
}
We set the value of this variable in the switch statement. We can remove the configuration of the table view cell from the switch statement and move it to the bottom of the implementation of the tableView(_:cellForRowAt:) method. That is a step in the right direction to avoid code duplication.
SettingsViewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let section = Section(rawValue: indexPath.section) else {
fatalError("Unexpected Section")
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.reuseIdentifier, for: indexPath) as? SettingsTableViewCell else {
fatalError("Unable to Dequeue Settings Table View Cell")
}
// Helpers
var viewModel: SettingsPresentable?
switch section {
case .time:
guard let timeNotation = TimeNotation(rawValue: indexPath.row) else {
fatalError("Unexpected Index Path")
}
// Initialize View Model
viewModel = SettingsTimeViewModel(timeNotation: timeNotation)
case .units:
guard let unitsNotation = UnitsNotation(rawValue: indexPath.row) else {
fatalError("Unexpected Index Path")
}
// Initialize View Model
viewModel = SettingsUnitsViewModel(unitsNotation: unitsNotation)
case .temperature:
guard let temperatureNotation = TemperatureNotation(rawValue: indexPath.row) else { fatalError("Unexpected Index Path") }
// Initialize View Model
viewModel = SettingsTemperatureViewModel(temperatureNotation: temperatureNotation)
}
if let viewModel = viewModel {
// Configure Cell
cell.mainLabel.text = viewModel.text
cell.accessoryType = viewModel.accessoryType
}
return cell
}
There is one subtle improvement we can make. We don't need to declare viewModel as an optional. We can declare it as a constant of type SettingsPresentable. We use a self-executing closure and move the switch statement into the closure. In each case, we return the view model instead of modifying the value of viewModel.
// Helpers
let viewModel: SettingsPresentable = {
switch section {
case .time:
guard let timeNotation = TimeNotation(rawValue: indexPath.row) else {
fatalError("Unexpected Index Path")
}
// Initialize View Model
return SettingsTimeViewModel(timeNotation: timeNotation)
case .units:
guard let unitsNotation = UnitsNotation(rawValue: indexPath.row) else {
fatalError("Unexpected Index Path")
}
// Initialize View Model
return SettingsUnitsViewModel(unitsNotation: unitsNotation)
case .temperature:
guard let temperatureNotation = TemperatureNotation(rawValue: indexPath.row) else { fatalError("Unexpected Index Path") }
// Initialize View Model
return SettingsTemperatureViewModel(temperatureNotation: temperatureNotation)
}
}()
This also means that we don't need to safely unwrap viewModel. I explain this pattern in more detail in What Are Self-Executing Closures.
// Configure Cell
cell.mainLabel.text = viewModel.text
cell.accessoryType = viewModel.accessoryType
What's Next?
Protocols work very well with the Model-View-ViewModel pattern. In the next episode, we take it one step further.