Mastering MVVM with SwiftUI

38 Episodes

Episode 1


Views, Models, and View Models

If you have been reading or watching Cocoacasts for some time, then you know that the Model-View-ViewModel pattern is one of my preferred patterns for architecting applications. I have been using MVVM for several years in various projects and it continues to prove its value in every project I work on.

Episode 2


The Problem

With this and the next episode, I want to make sure you choose for the Model-View-ViewModel pattern for the right reasons. I want to avoid that you adopt MVVM in a project because you were told it is a sound architecture or because you think you need view models if you use SwiftUI. In this episode, I want to highlight a few problems a typical SwiftUI application can suffer from. Those problems can be resolved by the Model-View-ViewModel pattern with relative ease.

Episode 3


A Solution

In the previous episode, I highlighted a few problems a typical SwiftUI application can suffer from. These problems can be resolved in several ways. In this series, we explore how the Model-View-ViewModel pattern solves these problems. The goal is to put the views of the application on a diet and decouple view logic from business logic.

Episode 4


Fixing Code Smells

In the previous episode, we solved some of the problems we discussed earlier in this series. At the same time, we introduced a few code smells. We address those code smells in this episode.

Episode 5


Hiding the Model Layer

Remember that a view should be dumb. It doesn't care what it displays. The notes view doesn't match that description. It can access the array of notes through its view model. That is a code smell and something we need to change. You learn how to do that in this episode.

Episode 6


Meet Thunderstorm

In the past few episodes, you learned how the Model-View-ViewModel pattern can transform a project and its architecture. In the remainder of this series, we build an application from scratch. Through that process, you deepen your understanding of the MVVM pattern and how it affects a project's architecture, testability, and structure.

Episode 7


Setting Up the Project

In this episode, we set up the project for Thunderstorm, the weather application we build in this series. We make a few changes to prepare the project for the MVVM pattern.

Episode 8


Populating the Locations View

With the project set up, we can focus on adding features to the weather application we are building. In this episode, we populate the locations view.

Episode 9


Displaying Weather Data

In the previous episode, we wrote quite a bit of code that we wouldn't have written if we were building a SwiftUI application without the Model-View-ViewModel pattern. This is fine since we have laid a foundation we can take advantage of in the next few episodes. In this episode, we continue to build out the user interface of the locations view by displaying weather data.

Episode 10


Populating the Location View

In this episode, we implement the location view. The user can navigate to the location view by tapping a location in the locations view. The location view displays the current weather conditions at the top and a forecast at the bottom.

Episode 11


Displaying Weather Data in the Location View

In this episode, we display weather data in the location view. Remember that the location view displays two subviews or child views, the current conditions view and the forecast view. These views are responsible for displaying the weather data for a location.

Episode 12


Displaying Weather Data in the Forecast View

In this episode, we focus on the forecast view, a subview or child view of the location view. The forecast view displays the temperature for each day of the weather forecast. Let's add a few more details to the items of the vertical grid.

Episode 13


Adding Locations

The locations view displays a static list of locations. While that has been useful to implement the user interface of the locations view, in the next few episodes we focus on adding locations.

Episode 14


Populating the Add Location View

In the next few episodes, we populate the AddLocationView. We break this task up into several smaller steps. We first populate the AddLocationView with stub data. Later in this series, we replace the stub data with data provided by the Core Location framework.

Episode 15


Integrating the Geocoding Service

The AddLocationView displays stub data for the time being. We change that in this and the next episode by integrating the Core Location framework. Apple's Core Location framework defines an API for forward geocoding addresses. We use that API to convert an address to a collection of placemarks. The application converts the placemarks to Location objects the AddLocationView can display.

Episode 16


Integrating the Core Location Framework

The GeocodingClient class returns stub data for the time being. In this episode, we integrate the Core Location framework and take advantage of the geocoding APIs it offers. We use the geocoding APIs to forward geocode the address the user enters in the TextField of the AddLocationView to a collection of `Location` objects.

Episode 17


Improving the Testability of the Geocoding Client

In the previous episode, we made the Core Location framework a dependency of the project. That is fine, but we don't want a dependency to compromise the testability of the project. In this episode, we use a proven and familiar pattern to improve the testability of the GeocodingClient class.

Episode 18


Optimizing the User Experience

The user can add locations using the AddLocationView, but there is room for improvement. The user experience isn't optimal at the moment. The AddLocationView doesn't show a progress view when the application is forward geocoding the address the user entered and the user is faced with an empty view if no matches are found. That is something we address in this episode.

Episode 19


Refactoring the Add Location View

In the previous episode, we declared the State enum. It defines the possible states of the AddLocationView. In this episode, we integrate the State enum into the AddLocationView.

Episode 20


Persisting Locations in User Defaults

You may remember that the LocationsView displays a static list of locations. We need to change that if we want to put the AddLocationView to use. We keep it simple and store the list of locations in the user's defaults database. Let's get to work.

Episode 21


Updating Locations in User Defaults

In this episode, we revisit the addLocation(with:) method of the AddLocationViewModel class. In that method, the view model stores the location the user selected in the user's defaults database.

Episode 22


Decoupling User Defaults

The user's defaults database acts as the store of the weather application we are building. That is fine, but most objects shouldn't be aware of that implementation detail. The LocationsViewModel and AddLocationViewModel classes are tightly coupled to the UserDefaults class, but that isn't necessary. In this episode, we decouple the LocationsViewModel and AddLocationViewModel classes from the UserDefaults class.

Episode 23


Defining the Weather Data Model

It is time to focus on one of the core aspects of the weather application we are building, fetching and displaying weather data. Thunderstorm displays placeholder data for the time being. That is something we change in this and the next episodes.

Episode 24


Fetching Weather Data

In this episode, we add the ability to fetch weather data from the Clear Sky API and decode a WeatherData object from the API response. We apply a pattern that should feel familiar by now.

Episode 25


Injecting Base URL and API Key

In the previous episode, we hard-coded the base URL and the API key of the Clear Sky API in the WeatherClient class. I'm not a fan of hard-coding configuration details. In this episode, we explore an alternative approach.

Episode 26


Refactoring the Location Cell View Model

With the WeatherService protocol in place, we can refactor the view models that need to provide their views with weather data. In this episode, we focus on the LocationCell by refactoring the LocationCellViewModel class.

Episode 27


Avoiding Code Duplication

In the previous episode, we used the MeasurementFormatter class to format the raw values the Clear Sky API returns. This is convenient and we use the MeasurementFormatter class several more times in the next few episodes. In this episode, we create an elegant API that wraps around the MeasurementFormatter API to avoid code duplication.

Episode 28


Refactoring the Current Conditions View Model

In this episode, we refactor the CurrentConditionsViewModel struct. Remember that the CurrentConditionsViewModel struct drives the CurrentConditionsView, the top section of the LocationView. The changes we need to make are small.

Episode 29


Refactoring the Forecast View Model

In this episode, we refactor the ForecastViewModel struct and the ForecastCellViewModel struct. The ForecastViewModel struct drives the ForecastView, the bottom section of the LocationView. Let's get to work.

Episode 30


Refactoring the Location View Model

The LocationView still displays stub data. That is something we change in this episode. The LocationViewModel struct is responsible for fetching the weather data the LocationView displays. It uses that weather data to create view models for the CurrentConditionsView and the ForecastView.

Episode 31


Defining the States of the Location View

The LocationView is always in one of two states, fetching weather data or displaying weather data. It is the LocationView itself that implicitly defines these states and that is limiting. Not only is it limiting, we cannot write unit tests for the states of the LocationView. In this episode, I show you a solution that is testable and extensible.

Episode 32


Deleting Locations

The last feature we need to add is the ability to delete locations. The changes we need to make are small, but there is a problem we need to address. Let's start by adding a button to the CurrentConditionsView.

Episode 33


Injecting Dependencies with Swinject

We hit a roadblock in the previous episode. The CurrentConditionsViewModel struct needs access to a Store object, but it is tedious to pass a Store object from one view model to the next. In this episode, we use a dependency injection library, Swinject, to make this much less of a problem.

Episode 34


Unit Testing a Simple View Model

One of the key benefits of the Model-View-ViewModel pattern is improved testability. It isn't possible to write unit tests for the SwiftUI views we created, but we can unit test the view models that drive those views. The good news is that writing unit tests for a well-designed view model isn't difficult.

Episode 35


Unit Testing a Complex View Model

In the previous episode, we wrote unit tests for a simple view model, the AddLocationCellViewModel struct. In this episode, we take it up a notch and write unit tests for a complex view model. We take the AddLocationViewModel class as an example.

Episode 36


Writing Asynchronous Unit Tests

In this episode, we continue where we left off in the previous episode, that is, writing unit tests for the AddLocationViewModel class. This episode is an important one because it illustrates the impact asynchronous code can have on the unit tests you write.

Episode 37


The Challenges of Asynchronous Unit Tests

In this episode, we finish the unit test we started in the previous episode. Even though the implementation of the addLocation(with:) method of the AddLocationViewModel class isn't overly complex, writing a unit test for it is quite the challenge. Let's continue where we left off.