Learn Swift and iOS Development
Master iOS development through in-depth tutorials and comprehensive courses on Swift, SwiftUI, UIKit, Core Data, and more.
Master iOS development through in-depth tutorials and comprehensive courses on Swift, SwiftUI, UIKit, Core Data, and more.
7:34
I'd like to end this series by taking advantage of the recently introduced Observable macro. The changes we need to make are small and focused, but we also run into a few issues. Let's get started.
9:08
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.
8:10
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.
9:12
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.
9:49
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.
7:03
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.
5:47
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.
7:50
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.
5:27
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.
4:49
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.
3:02
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.
9:03
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.
9:39
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.
7:50
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.
5:33
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.
9:22
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.
8:18
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.
8:24
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.
8:00
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.
5:09
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.
8:44
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.
3:55
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.
7:23
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.
11:29
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.
7:51
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.
10:08
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.
11:09
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.
8:37
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.
8:22
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.
8:17
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.
10:17
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.
6:33
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.
5:50
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.
6:12
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.
7:51
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.
7:05
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.
4:49
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.
3:37
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.
9:08
It is time to unit test the AddLocationViewModel class. Create a new unit test case class in the Test Cases group of the CloudyTests target and name it AddLocationViewModelTests.swift.
8:28
It is time to unit test the AddLocationViewModel class. Create a new unit test case class in the Test Cases group of the CloudyTests target and name it AddLocationViewModelTests.swift.
7:15
If we want to unit test the AddLocationViewModel class, we need the ability to stub the responses of the geocoding requests we make to Apple's location services. Only then can we write fast and reliable unit tests. Being in control of your environment is essential if your goal is creating a fast and robust test suite.
7:22
If we want to unit test the AddLocationViewModel class, we need the ability to stub the responses of the geocoding requests we make to Apple's location services. Only then can we write fast and reliable unit tests. Being in control of your environment is essential if your goal is creating a fast and robust test suite.
4:40
We put the foundation in place in the previous episode by refactoring the AddLocationViewModel class. We complete the integration with the Combine framework in this episode by refactoring the AddLocationViewController class.
10:51
Before we integrate the Combine framework into the project, I want to briefly revisit the RxSwift and RxCocoa integration. RxSwift is a reactive extension for the Swift language and, as the name suggests, RxCocoa reactifies the Cocoa components you use day in day out.
3:51
RxSwift has been around for many years, but it isn't the only option you have. In 2019, Apple introduced Combine, a system framework that brings reactive programming to Apple's platforms. The framework provides a declarative API for processing values of time.
5:31
The changes we made in the previous episode broke the application. To fix what we broke, we need to update the implementation of the AddLocationViewController class. Open AddLocationViewController.swift and add an import statement for RxSwift and RxCocoa at the top.
7:04
With RxSwift and RxCocoa integrated into the project, it is time to refactor the AddLocationViewModel class. Make sure you open the workspace CocoaPods created for us in the previous episode. Open AddLocationViewModel.swift and add an import statement for RxSwift and RxCocoa at the top.
2:02
There are several options to integrate RxSwift and RxCocoa into an Xcode project. The README of the RxSwift project shows you the different possibilities. I mostly use CocoaPods and that is the approach I take in this series. If you would like to follow along with me, make sure you have CocoaPods installed. You can find more information about installing CocoaPods on the CocoaPods website.
2:01
Before we refactor the AddLocationViewModel class, I would like to take a few minutes to explain my motivation for using RxSwift and RxCocoa. There are several reasons.
5:01
It is time to refactor the AddLocationViewController class. We start by removing any references to the Core Location framework, the import statement at the top, the geocoder property, the geocode(addressString:) method, and the processResponse(withPlacemarks:error:) method.
8:21
Before we use RxSwift and Combine to improve the current implementation of the Model-View-ViewModel pattern, I want to show you how you can roll your own solution using closures. I like to refer to this solution as DIY bindings or Do It Yourself bindings.
3:08
How do we bind the various components of the Model-View-ViewModel pattern together? That is the question we focus on in the next few episodes. It is a problem most developers new to the MVVM pattern struggle with. It isn't difficult to use the Model-View-ViewModel pattern to push data from the controller layer to the view layer. We already covered that extensively in this series. What do we need to change to automatically update the user interface if the user interacts with the application or the environment changes?
5:28
You should now have a good understanding of what the Model-View-ViewModel pattern is and how it can be used to cure some of the problems the Model-View-Controller pattern suffers from. We can do better, though. Data is currently flowing in one direction. The view controller asks its view model for data and populates the view it manages. This is fine and many projects can greatly benefit from this lightweight implementation of the Model-View-ViewModel pattern.
4:07
Writing units tests for the view models of the WeekViewController class is just as easy as writing unit tests for the DayViewModel struct. We start with the unit tests for the WeekViewModel struct.
8:17
Unit testing the DayViewModel struct isn't very different from unit testing the view models of the SettingsViewController class. The only tricky aspect is creating a DayViewModel object in a unit test.
7:59
In this episode, we unit test the view models of the settings view controller. We start with the SettingsTimeViewModel struct. Create a new file in the Test Cases group and select the Unit Test Case Class template.
2:47
Because we moved a fair bit of logic from the view controllers of the project to the view models, we gained an important advantage, improved testability. As I mentioned earlier in this series, unit testing view controllers is known to be difficult. View models, however, are easy to test and that is the focus of the next few episodes.
6:26
Let's apply what we learned in the previous episodes to the week view controller. The week view controller currently configures the cells of its table view. That is something we want to change. The refactoring of the week view controller involves four steps. We create a view model for each table view cell. The WeekViewModel struct generates a view model for each table view cell. We define a protocol to which the view models for the table view cells conform. The WeatherDayTableViewCell class has the ability to configure itself using a view model.
4:06
The settings view controller manually configures each table view cell, using a view model. Why can't the table view cell configure itself? Can we make it autoconfigurable?
4:05
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.
6:56
The Model-View-ViewModel pattern isn't only useful for populating data-driven user interfaces. In this episode, I show you how to apply the MVVM pattern in the SettingsViewController class.
8:08
In this episode, we shift focus to the WeekViewController class. To adopt the Model-View-ViewModel pattern in the WeekViewController class, we start by creating a type for the view model of the week view controller. Create a new file in the View Models group and name the file WeekViewModel.swift.
5:05
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.
8:03
In this episode, we create a view model for the day view controller. Fire up Xcode and open the starter project of this episode. We start by creating a new group, View Models, in the Weather View Controllers group. I prefer to keep the view models close to the view controllers in which they are used.
2:32
Before you create your first view model, I want to revisit the internals of the Model-View-ViewModel pattern. We need to keep the following in mind. The model is owned by the view model. The controller doesn't know about and cannot access the model. The controller owns the view model and the view model doesn't know about the controller it is owned by.
3:43
Now that you have an idea of the ins and outs of Cloudy, I'd like to take a few minutes to highlight some of Cloudy's issues. Keep in mind that Cloudy is a small project. The problems we're going to fix with the Model-View-ViewModel pattern are less apparent, which is why I'd like to highlight them before we fix them.
7:46
In the remainder of this series, we refactor an application that is built with the Model-View-Controller pattern and make it adopt the Model-View-ViewModel pattern instead. This will answer two important questions. (1) What are the shortcomings of the MVC pattern? (2) How can the MVVM pattern help resolve these shortcomings?
4:36
In this episode, we take a closer look at the internals of the Model-View-ViewModel pattern. We explore what MVVM is and how it works.
7:20
Model-View-Controller, or MVC for short, is a widely used design pattern for architecting software applications. Cocoa applications are centered around the Model-View-Controller pattern and many of Apple's frameworks make heavy use of the Model-View-Controller pattern.
3:25
Welcome to Mastering MVVM With Swift. In this series, you learn the ins and outs of the Model-View-ViewModel pattern. The goal is to provide you with the ingredients you need to implement the Model-View-ViewModel pattern in your own projects.
5:54
A few months ago, I had an interesting conversation with a reader about the Model-View-ViewModel pattern. Let's call him John. The questions John asked me show that developers new to MVVM often miss or overlook the more profound benefits the pattern brings to the table. In this episode, I'd like to share with you the most important insights of my conversation with John.
The first programming language I picked up was PHP and, since it was my first experience programming, I immediately loved it. At that time, I had no idea what object-oriented programming was. The result was a lot of spaghetti code that did something.
Most Cocoa applications are powered by the Model-View-Controller pattern, MVC for short. MVC is easy to understand and has very little overhead. But those that have used the Model-View-Controller pattern for some time know that it isn't perfect. Not even close. The Model-View-ViewModel pattern offers an interesting alternative.
Yesterday, you learned how to use view models in a view controller that isn't driven by data. We refactored the settings view controller of Cloudy. But there is room for improvement. We are repeating ourselves in the tableView(_:cellForRowAt:) method of the UITableViewDataSource protocol. We can improve this using protocol-oriented programming.
In Mastering Model-View-ViewModel With Swift, we explore how you can use the Model-View-ViewModel pattern to simplify table views. The example we use in the course is Cloudy, a weather application powered by the Dark Sky API. The settings view of the application contains a handful of settings.
If you are familiar with the Model-View-Controller pattern, then you know that it splits an application up into three components or layers, the view layer, the model layer, and the controller layer.