If you are not sure how the various pieces of the Model-View-ViewModel pattern fit together, then this episode will be helpful. In this episode, we put the DayViewModel struct we created in the previous episode to use. This means that we need to refactor the DayViewController and the RootViewController classes.
Adding the View Model to the Day View Controller
We start with the DayViewController class. We remove the now property and define a property, viewModel, of type DayViewModel?. We define a didSet property observer for the viewModel property. Every time the viewModel property is set, updateView() is invoked.
DayViewController.swift
var viewModel: DayViewModel? {
didSet {
updateView()
}
}
Creating the View Model in the Root View Controller
Because we removed the now property of the DayViewController class, we need to update the implementation of the RootViewController class. The root view controller no longer passes the WeatherData object to the day view controller. The root view controller creates a DayViewModel object using the weather data it receives from the data manager. It then passes the view model to the day view controller by setting its viewModel property. This is also know as property injection, a type of dependency injection.
Open RootViewController.swift and navigate to the fetchWeatherData() method. In the completion handler of the weatherDataForLocation(latitude:longitude:completion:) method, we create a DayViewModel object and assign it to the viewModel property of the day view controller. If the data manager fails to fetch weather data, we set the viewModel property of the day view controller to nil.
RootViewController.swift
private func fetchWeatherData() {
...
// Fetch Weather Data for Location
dataManager.weatherDataForLocation(latitude: latitude, longitude: longitude) { [weak self] (result) in
switch result {
case .success(let weatherData):
// Configure Day View Controller
self?.dayViewController.viewModel = DayViewModel(weatherData: weatherData)
// Configure Week View Controller
self?.weekViewController.week = weatherData.dailyData
case .failure:
// Notify User
self?.presentAlert(of: .noWeatherDataAvailable)
// Update Child View Controllers
self?.dayViewController.viewModel = nil
self?.weekViewController.week = nil
}
}
}
Earlier in this series, you learned that the controller shouldn't have direct access to the model. It is the view model that owns and knows about the model. The controller indirectly accesses the model through its view model.
The changes we made in this episode reflect this. The day view controller no longer has direct access to the model, the WeatherData object. It accesses the model indirectly through its view model.
Updating the Weather Data Container
The last piece of the puzzle is updating the updateWeatherDataContainerView(with:) method of the DayViewController class. First, we rename the weatherData parameter of the updateWeatherDataContainerView(with:) method to viewModel and change the type to DayViewModel. That makes more sense.
DayViewController.swift
private func updateWeatherDataContainerView(with viewModel: DayViewModel) {
...
}
We also update the updateView() method of the DayViewController class. We replace now with viewModel and pass the DayViewModel object to the updateWeatherDataContainerView(with:) method.
DayViewController.swift
private func updateView() {
activityIndicatorView.stopAnimating()
if let viewModel = viewModel {
updateWeatherDataContainerView(with: viewModel)
} else {
messageLabel.isHidden = false
messageLabel.text = "Cloudy was unable to fetch weather data."
}
}
The implementation of the updateWeatherDataContainerView(with:) method becomes much shorter thanks to the DayViewModel struct. The day view controller no longer needs to handle the raw values of the WeatherData object. It simply asks its view model for the data it needs to populate the view it manages.
DayViewController.swift
private func updateWeatherDataContainerView(with viewModel: DayViewModel) {
weatherDataContainerView.isHidden = false
dateLabel.text = viewModel.date
timeLabel.text = viewModel.time
iconImageView.image = viewModel.image
windSpeedLabel.text = viewModel.windSpeed
descriptionLabel.text = viewModel.summary
temperatureLabel.text = viewModel.temperature
}
The foundation we laid in the previous episode is paying off. To populate the date label, the day view controller asks the view model for its date property, the computed property we implemented in the previous episode. It configures the time label with the value of the time property of the DayViewModel struct and so on and so forth. The resulting implementation is short, readable, and focused.
I am sure you agree that deleting code is one of the more enjoyable aspects of software development. The day view controller is leaner and more focused. It presents data and responds to user interaction. That is what controllers are meant to do.
Build and Run
Run the application to make sure we didn't break anything. If something doesn't look quite right, then you probably made a mistake in the view model.
Even though the day view controller drives a simple user interface, it illustrates the basics of the Model-View-ViewModel pattern. It shows how MVVM can help keep the controllers of your project in check. The Model-View-ViewModel pattern delegates tasks and responsibilities of the controller to the view model.
What's Next?
I have used the Model-View-ViewModel pattern in a wide range of projects, small and large, and the results are impressive. Controllers no longer tend to be massive and the testability of your project improves dramatically. We are only scratching the surface, though. In the next episode, we shift focus to the week view controller.