To adopt the Model-View-ViewModel pattern in the WeekViewController
class, we first need to create a new view model. Create a new file in the View Models group and name the file WeekViewViewModel.swift.
Replace the import statement for Foundation with an import statement for UIKit and declare the WeekViewViewModel
struct.
WeekViewViewModel.swift
You already know what the next step is. We need to create a property for the weather data the view model manages. We name the property weatherData
and the property is of type array of WeatherDayData
instances.
WeekViewViewModel.swift
The WeekViewViewModel
struct will look a bit different from the DayViewViewModel
struct because it manages an array of model objects. Once we’re done refactoring the WeekViewController
class, it will no longer have a reference to the weather data. This means that the week view controller won’t know how many sections and rows the table view should display. That information needs to come from the view controller’s view model.
Let’s start by adding a new computed property to the WeekViewViewModel
struct, numberOfSections
. The numberOfSections
computed property returns the number of sections the week view controller should display in the table view it manages. The view controller currently display only one section, which means we return 1
.
WeekViewViewModel.swift
The view controller also needs to know how many rows that section contains. To answer this question, we add another computed property, numberOfDays
, which tells the view controller the number of days the view model has weather data for. We could implement a fancy method that accepts an index corresponding with the section in the table view, but I prefer to keep view models as simple and as dumb as possible. The view controller and its view model should only be given information they absolutely need to carry out their tasks.
WeekViewViewModel.swift
Up until now, we used computed properties to provide the view controller with the information it needs. It’s time for a few methods. These methods will provide the view controller with weather data for a particular day. This means that the view controller asks the view model for the weather data for a specific index. The index corresponds with a row in the table view.
Let’s start with the day label of the WeatherDayTableViewCell
. The view controller will ask its view model for a string that represents the day of the weather data. This means we need to implement a method in the WeekViewViewModel
struct that accepts an index of type Int
and returns a value of type String
.
WeekViewViewModel.swift
We first fetch the WeatherDayData
instance that corresponds with the index that’s passed to the day(index:)
method. We create a DateFormatter
instance and format the value of the time
property of the model.
WeekViewViewModel.swift
As I mentioned earlier in this book, I prefer to make the DateFormatter
instance a property of the view model. Let’s take care of that now. This is what the WeekViewViewModel
struct looks like so far.
WeekViewViewModel.swift
We can use the same approach to populate the date label of the WeatherDayTableViewCell
. Only the value of the date formatter’s dateFormat
property is different. You could argue that we could get away with only one DateFormatter
property. That’s a personal choice. It won’t dramatically impact performance since we only have a handful of table view cells to populate.
WeekViewViewModel.swift
Setting the text
property of the temperature label of the WeatherDayTableViewCell
is another fine example of the elegance and versatility of view models. Remember that the WeatherDayTableViewCell
displays the minimum and the maximum temperature for a particular day. The view model should provide the formatted string to the view controller so that it can pass it to the WeatherDayTableViewCell
.
In the temperature(for:)
method, we fetch the weather data, format the minimum and maximum temperatures using a helper method, format(temperature:)
, and return the formatted string as a result.
WeekViewViewModel.swift
The format(temperature:)
helper method isn’t complicated. It only prevents us from repeating ourselves.
WeekViewViewModel.swift
Populating the wind speed label and the icon image view is very similar to what we covered so far. Take a look at the implementations below.
WeekViewViewModel.swift
WeekViewViewModel.swift
With the WeekViewViewModel
struct ready to use, we shift focus to the RootViewController
class. First, however, we replace the week
property in the WeekViewController
class with a property named viewModel
of type WeekViewViewModel?
.
WeekViewController.swift
We can now update the RootViewController
class. The root view controller no longer passes the array of WeatherDayData
instances to the week view controller. Instead, the root view controller instantiates an instance of the WeekViewViewModel
using the array of WeatherDayData
instances and sets the viewModel
property of the week view controller.
Open RootViewController.swift and navigate to the fetchWeatherData()
method. In the completion handler of the weatherDataForLocation(latitude:longitude:completion:)
method, we create an instance of the WeekViewViewModel
struct and assign it to the viewModel
property of the week view controller.
RootViewController.swift
Revisit WeekViewController.swift. With the WeekViewViewModel
struct ready to use, it’s time to refactor the WeekViewController
class. This means we need to update the updateWeatherDataContainer(withWeatherData:)
method. We start by renaming this method to updateWeatherDataContainer()
. There’s no need to pass in the view model like we did in the DayViewController
class.
WeekViewController.swift
We also update the updateView()
method to reflect these changes. We also replace any references to the week
property with references to the viewModel
property.
WeekViewController.swift
The implementation of the UITableViewDataSource
protocol also needs some changes. As you can see, we use the methods we implemented earlier in the WeekViewViewModel
struct.
WeekViewController.swift
WeekViewController.swift
Thanks to the WeekViewViewModel
struct, we can greatly simplify the implementation of tableView(_:cellForRowAt:)
.
WeekViewController.swift
Last but not least, we can get rid of the DateFormatter
properties of the WeekViewController
. They’re no longer needed and that’s a very welcome change.
Even though we successfully implemented the Model-View-ViewModel pattern in the week view controller, later in this book, we use protocols to further simplify the implementation of the Model-View-ViewModel pattern in the week view controller.