MVVM Design Pattern For SwiftUI

Mehmet Ateş
IBTech
Published in
8 min readNov 20, 2022

SwiftUI is a great framework that has made things a lot easier since it came into our lives. To use it in the most effective and sustainable way, we must follow a certain pattern. Our topic is design patterns for SwiftUI 💙

It will be a good article series that we will examine 3 design patterns together. Our first stop is MVVM. Let’s get to know MVVM first.

A design pattern recommended by Apple for SwiftUI that works really well with Combine. Consisting of 3 layers, this design pattern consists of:

  • Model: Entities we know very well.
  • View: Again, the UI we’re very, very familiar with.
  • ViewModel: It’s the layer that matters to us that’s available to us with MVVM. The part where both business and logic operations are done.

Now that we have the summary information, let’s look at the relationship between these 3 layers.

MVVM
MVVM Feed

Let’s focus on the view model, which is important to MVVM. For a simple request, it does the following in order:

  • It is triggered by the View.
  • Triggers the service for the request.
  • The service sends a request to the API and receives an error or response.
  • It transmits the response or result it receives to the view model.
  • If the received response is an error, the view model returns an error response to the screen and completes the request.
  • If a successful response is received, it parses the model and processes the response.
  • Passes the rendered model back to the view as requested by the view.

So how does it work a logic?

  • It is triggered by the View.
  • It does what it wants in itself.
  • Reports the result of the operation to the view.

This is MVVM in its simplest form. Let’s see this in a real project, with real requests, real logic. I wrote an application for this.

Important Note

In order to access the contents in the project, you need to define an api key anywhere in the project as follows. You can get the key here.

struct AppEnvironments {
static let apiKey: String = "Your Key"
}

Let’s take a closer look and explore the beauties of both MVVM and SwiftUI together. Unit Tests included :)

We have 3 schemes in the application. These;

  • Home Screen: Our discovery screen, filling up with multiple requests. The first screen of the application.
  • Search Screen: A search screen where you can search for movies and TV shows any way you want.
  • Detail Screen: A screen where you can review the contents of movies or series.

We can start with the Home Screen feed.

As you can see, a view consisting of 1 header tabbar and 6 horizontal scrolls. Header tabbar is filled with top 10 items of popular tv content. So it contains 6 requests in total. Network manager throws the requests for us and sends the response or error to the view model.

Network Manager

This network manager I wrote only allows me to send requests and receive responses in accordance with the API I use. Now let’s come to the view model where we use the request function.

HomeViewModel
  • The first place we will concentrate is protocol. It has a dictionary containing our Media Sections, a boolean indicating the page state, and a function to update the page for the sections. It simply means that we will use them in the view and test them. Also, another thing we need to pay attention to is another protocol that our protocol is connected to.
  • Since the successful response results of the requests are the same, I wrote a protocol to avoid code duplication.
Requestable Media List Protocol
  • We saw that this hooks into our view model. So our ViewModel is actually an observable object. And it publishes the 2 objects it contains in the protocol.
  • The second point we need to focus on is exactly here. Because here, when one of the two objects changes, the view is informed about it. It does this by observing the view model.
  • In order to do this, it needs to define it as observed in the view. Otherwise it doesn’t make any sense.
  • For example, we can see that the loading state directly affects the content of the page. Just look at lines 16 to 20.

Well, we understand the requests. We already have a few more examples to reinforce. Does ViewModel have another task here? YES! All of the above actions happened automatically when the viewmodel was init. Did we mention triggering?

You will see that there is a method defined as Interface properties above. This method will increase the number of pages to load new movies as our horizontal media scrolls scroll sideways and call the function that will make a new request and do this. It’s too long a sentence 😅.

But that’s what it does :)

If we think a little bit, we can easily understand that it is an operation that is triggered by the view and takes place in the view model, as it is a situation that needs to be done as you scroll.

There is a nicer place where we can understand this. It’s also Search View. Let’s take a look at it.

Search View

Here we see the view trigger much more clearly. Every time our search key changes, the request is made again and the page is updated. And the only good thing that provides this is Observable annotation. Its structure is as follows;

The search key is constantly monitored by the view, and in fact, every hit we make on the keyboard changes that search key. In other words, the search key always changes in the view model and is pressed on the screen again because it is being watched.

Of course, one of SwiftUI’s magic modifiers, the searchable, should also be thanked for doing so :) They may sound complex or very simple. The only truth will be to experience. So let’s do a challenge with you.

The view model of the detail view, which is a view with a very complex structure, is not that clean. Can you refactor this? I did all the things I wanted you to experience in another branch. Let me tell you what you need to do.

First review this commit. You will see that the Homeview model is similar to the DetailViewModel and how I made it clean. Implement this in detail view.

Now fork the project and try doing this challenge. You must create and publish new objects. Maybe you don’t publish and see what happens. Share your refactor on twitter by tagging me 💙. So we can see lots of different codes.

Everything went well, let’s get to the test. You’ll see that we’re going to need some change here. Because Unit tests “are functions triggered?” It clarifies the question in our minds. Results are the task of integration tests. So where do we make changes?

Unfortunately, our project is not very testable in its current state. But we can make it testable. (This required a lot of changes. Please review the differences by switching between branches.)

I’ll just talk about the changes and tests we made for the HomeViewModel. Let’s start.

In HomeViewmodel, we could do our operations directly without defining an object with the singleton property of NetworkManager before. But when it came to mocking, this put us in a snag and made our code untestable. Therefore, I connected the network manager to the homeviewmodel with a protocol and mocked the network manager using the protocol.

Let me explain to you why I did this. We already know that when the HomeViewModel functions run, it throws a request. While doing this, it used to create a Network manager instantly, access its function and then destroy it. That is, the part I had to test was a momentary existence and disappearing with extinction.

With this change, HomeViewModel now has a network manager object and performs all operations on it. In this way, I can create a mock in my tests and perform operations on it. If you look carefully at the tests, it was enough for me to check that the network manager was triggered in all of them. Because the HomeViewModel’s job is to bring us the data.

All this time we talked about MVVM and tests. Want to learn a little about SwiftUI? Let’s look at performance improvements with SwiftUI

I move the cards back and forth. And you notice that some of the images are constantly being reloaded. This is a feature that reduces instant memory usage. And we use it in LazyVStack, LazyHStack, List etc components. On this page, you can experience that the same feature is used in sections by scrolling up and down.

The best optimization I did unconditionally was to install a lazy push system in nagivation. Instantly init the given page destination with SwiftUI NavigationLink. Even if the entire page is lazy, there are 32 instantly init DetailViewModels on this page. We prevented all view models from being created by wrapping this with lazynavigate and creating the view as soon as it is opened. You can access it at the link below.

Another great feature that SwiftUI provides is redacted. It will automatically make the page look like a placeholder and make it different until your data is loaded.

Everything looks perfect. So could it be better? Let’s decide this together. With Part 2, let’s write a new application with viper design pattern and see what has changed. I will update here when I add the article.

Also my SPM Packages I use in this project :)

Don’t forget to follow me to be informed about all my developments and new articles. Happy coding 💙🧡

--

--