MVVM in iOS with SwiftUI (Detailed Example + Pitfalls)

MVVM in iOS with SwiftUI (Detailed Example + Pitfalls)

Since the introduction of SwiftUI, the MVVM pattern has seen a new renaissance. Many developers believe that this particular pattern fits well with the SwiftUI data flow.

MVVM certainly has some good ideas, but it also brings along problems because of the various discording interpretations of the pattern and its rigidity.

In this article, we will see how MVVM fits in iOS apps written in SwiftUI, how to take advantage of its benefits, and how to avoid its problems.

Contents


Chapter 1

How MVVM improves the structure of iOS apps


Chapter 2

The model layer is the foundation of an app’s architecture


Chapter 3

Organizing the view layer and simplifying view models


Chapter 4

The networking infrastructure of an MVVM app


Chapter 5

Bringing an app together using view models

Architecting SwiftUI apps with MVC and MVVM

GET THE FREE BOOK NOW
Chapter 1:

How MVVM improves the structure of iOS apps

How MVVM improves the structure of iOS apps

MVVM is an architectural pattern that helps you structure your iOS apps. While it’s a derivation of MVC, it sports some unique ideas that fit well with how SwiftUI apps work.

The structure of the MVVM pattern and the roles of its layers

If I had to define MVVM quickly, I would say that it’s a variation of the MVC pattern.

But let’s proceed in order.

Like MVC, the Model-View-ViewModel pattern, or MVVM in short, is an architectural pattern that guides how you structure the code in your iOS apps. MVVM consists of three layers, from which it takes its name.

Diagram for the MVVM pattern in iOS apps

Each layer has a well-defined role in the app’s structure, helping you respect the separation of concerns design principle.

  • In the model layer, we find the Swift types representing your app’s data and its domain business logic.
  • The view layer is what the user sees on the screen. In iOS, it contains the SwiftUI views for your app, which display information and allow user interaction.
  • Finally, a view model connects a view to the app’s model. A view model contains the current state of a view, links to other parts of an app’s architecture (e.g., data storage or the network), and handles the user’s interaction.

The MVVM pattern is not unique to iOS. In fact, it was invented by Microsoft architects (of all people). It found its way into iOS apps only years after the release of the first iPhone.

Traditionally, Apple has followed the MVC pattern for both macOS and iOS apps. That changed with the introduction of SwiftUI.

MVVM uses binders to connect views and view models

Looking at the MVC pattern diagram, it does not take a genius to see that MVVM is, practically speaking, the same pattern. For that reason, I usually talk about architecture in terms of MVC, although I use ideas from both patterns.

The most crucial difference with MVC is that MVVM connects the view and the view model layers using a binder. This synchronizes the data between the two layers, removing boilerplate code.

That’s a natural part of SwiftUI.

In UIKit, developers used an FRP framework like RxSwift. SwiftUI instead uses, behind the scenes, Combine, Apple’s native reactive framework. But you don’t need to learn Combine to make SwiftUI apps.

So, in SwiftUI, the most significant difference between MVVM and MVC has disappeared. While SwiftUI offer several data flow mechanisms, there is only “one” way to connect objects and views:

  • An object must conform to the ObservableObject protocol.
  • Such an object must expose any property that affects the user interface using the @Published property wrapper.
  • Views connect to observed objects through the @StateObject, @ObservedObject, and @EnvironmentObjects property wrappers.

MVVM vs. MVC: local view models and of global controllers

There is another fundamental difference between MVC and MVVM.

In MVC, the emphasis has always been on making controllers globally shared objects. It was not uncommon to use singletons for that, although dependency injection became, with time, the preferred alternative.

In MVVM, instead, each view gets its separate view model. Objects, then, move from global to local.

In UIKit, both interpretations were possible because there was a fourth, not-so-hidden layer: view controllers. These sat squarely between views and controllers/view models. Since they were required by the framework, it was impossible to get rid of them.

That does not happen in SwiftUI. If you make an iOS app with more than one screen, you soon conclude that both local and global states are needed.

That’s why SwiftUI offers three property wrappers instead of just one. So, again the difference between MVC and MVVM disappears. We can have both controllers (global) and view models (local) in a full app. 

Chapter 2:

The model layer is the foundation of an app’s architecture

The model layer is the foundation of an app’s architecture

Any iOS app needs solid foundations or the whole castle crumbles. These foundations are the model types, which represent an app’s data.

These are not inert values, though. They contain the domain business logic and the code for data transformation.

Creating model types to represent the data of an app

As an example, we will build a small app for Hacker News, a news website for developers like Reddit, known for its (debatable) quality. 

We will use its simple web API to fetch the top 10 news stories from the best stories page.

Mockup for the Hacker News app to illustrate the MVVM pattern in SwiftUI

You can find the complete Xcode project on GitHub.

As it’s often the case, we will start creating the model layer of our app. These are the easiest to implement and the foundation for the whole app’s architecture.

Model types only contain data and the code that manipulates it. They should not know anything about data storage, networking, or how data is presented to the user. In short, they should not know anything about the other layers of the MVVM pattern.

The Hacker News API uses a single item entity to represent all its data. Stories, comments, jobs, etc. are all items. So, creating a corresponding Swift type is straightforward:

struct Item: Identifiable {
	let id: Int
	let commentCount: Int
	let score: Int
	let author: String
	let title: String
	let date: Date
	let url: URL
}

JSON decoding goes inside the model layer

Since the Hacker News API returns data in JSON format, our Item struct must conform to the Decodable protocol and provide some coding keys to map the JSON fields to our properties. You can read more about JSON decoding in my Codable article.

extension Item: Decodable {
	enum CodingKeys: String, CodingKey {
		case id, score, title, url
		case commentCount = "descendants"
		case date = "time"
		case author = "by"
	}
}

This is an example of code that manipulates data. Model types are not just empty containers. Data transformation code goes into model types together with the domain business logic.

Granted, our Item type has no business logic. That often happens for data we fetch from a web API. You can find an example in my free guide on MVC and MVVM in SwiftUI.

While we are at it, we can also create some test data we will use later for our SwiftUI previews. First, we need to grab the information for a story from the API, which we can save in a .json file in our Xcode project.

{
	"by" : "theafh",
	"descendants" : 312,
	"id" : 24777268,
	"kids" : [ 24778001, 24788825, 24777957, 24778210, 24778026, 24779203, 24778283, 24780437, 24778385, 24779634, 24779048, 24777921, 24779934, 24787940, 24781384, 24779512, 24784656, 24782672, 24779975, 24779050, 24787912, 24785350, 24784794, 24785141, 24780574, 24778706, 24778457, 24780008, 24780571, 24779757, 24785643, 24778735, 24779575, 24778254, 24777996, 24778341, 24777945, 24777875, 24779393, 24780180, 24782796, 24779270, 24780626, 24779061, 24778700, 24781183, 24779653, 24779074, 24778439, 24777831, 24779329, 24778009 ],
	"score" : 1082,
	"time" : 1602687710,
	"title" : "Room-Temperature Superconductivity Achieved for the First Time",
	"type" : "story",
	"url" : "https://www.quantamagazine.org/physicists-discover-first-room-temperature-superconductor-20201014/"
}

Then, we decode it inside a TestData structure to make it easy to use in any SwiftUI preview.

struct TestData {
	static let story: Item = {
		let url = Bundle.main.url(forResource: "Story", withExtension: "json")!
		let data = try! Data(contentsOf: url)
		let decoder = JSONDecoder()
		decoder.dateDecodingStrategy = .secondsSince1970
		return try! decoder.decode(Item.self, from: data)
	}()
}

Again, refer to my Codable article linked above for more details about JSON and test data.

Chapter 3:

Organizing the view layer and simplifying view models

Organizing the view layer and simplifying view models

SwiftUI makes it simple to structure views, but it also makes it simple to couple their code to other types, making them less reusable. In this chapter, we will see how to keep view types decoupled from the rest of the app’s code.

Views should be independent of model types

We will now jump to the other edge of the MVVM pattern and create our views.

I am skipping the view model layer because it’s the most complicated. Moreover, it’s hard to write a view model’s code when we don’t have its related view.

We can start with some simple views to represent the position, upvotes, and comments for each row in our News screen.

struct Badge: View {
	let text: String
	let imageName: String
	
	var body: some View {
		HStack {
			Image(systemName: imageName)
			Text(text)
		}
	}
}

struct Position: View {
	let position: Int
	
	var body: some View {
		ZStack {
			Circle()
				.frame(width: 32.0, height: 32.0)
				.foregroundColor(.teal)
			Text("\(position)")
				.font(.callout)
				.bold()
				.foregroundColor(.white)
		}
	}
}

struct NewsView_Previews: PreviewProvider {
	static var previews: some View {
		Group {
			Position(position: 1)
			Badge(text: "1.234", imageName: "paperplane")
		}
		.previewLayout(.sizeThatFits)
	}
}

The Xcode preview for the components of the News screen

I could have used the Label type of SwiftUI instead of creating the Badge view, but the former has a spacing that does not fit my mockup.

What is important here is that our two views are entirely independent of our Item structure. Instead, they use simple Swift types like Int and String. This is an excellent practice to keep your types as loosely coupled as possible so that changes break as little code as possible.

Formatting code should be kept out of views and view models

The view for a single story also looks straightforward. But here we have to pause for a moment.

Our UI requires a lot of data formatting:

  • The numbers for upvotes and comments need decimal separators;
  • We only need to show the domain name for a story, not its full URL;
  • A story doesn’t have a date but shows how much time has passed since its submission.

Formatting is related to the visual representation of data, so its code does not belong to the Item structure.

At the same time, we want to keep views independent and reusable by using simple Swift types. So, where do we put our formatting code?

Some proponents of MVVM think that formatting code should go into view models. But that’s a mistake.

First of all, this would overload view models with responsibilities. Moreover, the formatting code needs to be reusable and not tied to a single view or view model.

While we only have a single screen in our sample app, that rarely happens. A separate screen showing each story’s comments, as it happens on the Hacker News website, would need to format data in the same way.

Luckily, Swift offers a perfect solution: extensions.

extension URL {
	var formatted: String {
		(host ?? "").replacingOccurrences(of: "www.", with: "")
	}
}

extension Int {
	var formatted: String {
		let formatter = NumberFormatter()
		formatter.numberStyle = .decimal
		return formatter.string(from: NSNumber(value: self)) ?? ""
	}
}

extension Date {
	var timeAgo: String {
		let formatter = RelativeDateTimeFormatter()
		formatter.unitsStyle = .short
		return formatter.localizedString(for: self, relativeTo: Date())
	}
}

Don’t follow the MVVM pattern too strictly

Now, I have a question for you.

To which layer of MVVM does the formatting code above belong?

To be honest, I don’t think that’s a question that makes much sense. This shows you the dangers of adhering too strictly to a particular design pattern.

It looks like the code above belongs to the model layer since we are extending data types like Int, URL, and Date. At the same time, our extensions do not affect those types. We own that code, and we only use it in the view layer.

We can even place it inside the same files as our views. If I had to find a definite answer, I would say that our extensions are still part of the view layer.

Which brings me to the next point.

Keeping the view and model layers decoupled using Swift extensions

This could be a possible implementation for the view representing a story.

struct Story: View {
	let position: Int
	let item: Item
	
	var body: some View {
		HStack(alignment: .top, spacing: 16.0) {
			Position(position: position)
			VStack(alignment: .leading, spacing: 8.0) {
				Text(item.title)
					.font(.headline)
				Text(footnote)
					.font(.footnote)
					.foregroundColor(.secondary)
				ZStack(alignment: Alignment(horizontal: .leading, vertical: .center)) {
					Badge(text: item.score.formatted, imageName: "arrowtriangle.up.circle")
						.foregroundColor(.teal)
					Badge(text: item.commentCount.formatted, imageName: "ellipses.bubble")
						.padding(.leading, 96.0)
						.foregroundColor(.orange)
				}
				.font(.callout)
				.padding(.bottom)
			}
		}
		.padding(.top, 16.0)
	}
	
	var footnote: String {
		item.url.formatted
			+ " - \(item.date.timeAgo)"
			+ " - by \(item.author)"
	}
}

This view is coupled with our Item structure.

In a simple app, I would consider that to be acceptable. There is no need to complicate your code and keep it as generic as possible when that’s not needed.

In more complex apps, though, you often need to reuse UI components with different data types. Moreover, the Story view above embeds formatting code that might not apply to all data.

In that case, we need to decouple the Story view from the Item model type.

struct Story: View {
	let position: Int
	let title: String
	let footnote: String
	let score: String
	let commentCount: String
	
	var body: some View {
		HStack(alignment: .top, spacing: 16.0) {
			Position(position: position)
			VStack(alignment: .leading, spacing: 8.0) {
				Text(title)
					.font(.headline)
				Text(footnote)
					.font(.footnote)
					.foregroundColor(.secondary)
				ZStack(alignment: Alignment(horizontal: .leading, vertical: .center)) {
					Badge(text: score, imageName: "arrowtriangle.up.circle")
						.foregroundColor(.teal)
					Badge(text: commentCount, imageName: "ellipses.bubble")
						.padding(.leading, 96.0)
						.foregroundColor(.orange)
				}
				.font(.callout)
				.padding(.bottom)
			}
		}
		.padding(.top, 16.0)
	}
}

This does not solve our problem, though. It only moves it up the view hierarchy. The view that contains the Story type will need to use the code we just removed.

Here, Swift extensions come to our rescue again.

extension Story {
	init(position: Int, item: Item) {
		self.position = position
		title = item.title
		score = item.score.formatted
		commentCount = item.commentCount.formatted
		footnote = item.url.formatted
			+ " - \(item.date.timeAgo)"
			+ " - by \(item.author)"
	}
}

We can now initialize our view using an Item value, while its internal code uses only simple Swift types.

We can even export our Story view to another app without the Item type. All we need is a new extension using the model types of the destination project.

We can now use this new initializer in two places. The first is inside the SwiftUI preview.

struct NewsView_Previews: PreviewProvider {
	static var previews: some View {
		Group {
			Story(position: 1, item: TestData.story)
			Position(position: 1)
			Badge(text: "1.234", imageName: "paperplane")
		}
		.previewLayout(.sizeThatFits)
	}
}

Xcode preview for the row view

The second is in the view for the whole News screen.

struct NewsView: View {
	let stories: [Item]
	
	var body: some View {
		List(stories.indices) { index in
			Story(position: index + 1, item: stories[index])
		}
		.navigationTitle("News")
	}
}

Xcode preview for the complete News view

Chapter 4:

The networking infrastructure of an MVVM app

The networking infrastructure of an MVVM app

Networking is another of the weak points of the standard approach to MVVM. Many developers put networking code inside view models. It’s better to keep such code separate to make it extensible and avoid repetition.

Should networking code go into view models?

Our app needs to fetch data through the Hacker News API. Networking code clearly does not go into either the model or the view layers.

This is another long-standing debate about MVVM, where some proponents insist that networking code goes inside a view model?

I beg to differ.

If you look at it from the point of view of reusing code, that’s a mistake. While a view model clearly needs to trigger network requests to fetch data, networking code is filled with boilerplate.

That’s code that is clearly going to be repeated in every view model that performs network requests.

Here, I think the culprit is the use of reactive frameworks like RxSwift and Combine. All that boilerplate code gets lost into the glue code that connects streams of data.

But that’s just replacing some boilerplate with some other boilerplate.

Creating a separate networking layer to keep boilerplate code out of view models

A better approach to iOS networking in Swift is to build a separate infrastructure for API requests.

Our sample app doesn’t need a networking hierarchy as complex as the one in that article. Still, we can use a generic network request class, which simplifies our view model code and is easy to extend.

To fetch the best stories from the API, we need to perform two types of requests.

  • Usually, REST APIs return arrays of JSON objects with the full data. The best stories endpoint, instead, only returns an array of IDs.
  • After getting those IDs, we need to call the story endpoint several times to fetch each story’s data.

So, our API request class needs to handle different types of JSON data.

class APIRequest {
	let url: URL
	
	init(url: URL) {
		self.url = url
	}
	
	func perform<T: Decodable>(with completion: @escaping (T?) -> Void) {
		let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
		let task = session.dataTask(with: url) { (data, _, _) in
			guard let data = data else {
				completion(nil)
				return
			}
			let decoder = JSONDecoder()
			decoder.dateDecodingStrategy = .secondsSince1970
			completion(try? decoder.decode(T.self, from: data))
		}
		task.resume()
	}
}

In short, the perform(with:) method uses a URLSession to fetch data and then decodes it with a JSONDecoder. Since it uses a Swift generic, we can use this method for any Decodable data.

Again, refer to the article about REST APIs I linked above for a full explanation.

Chapter 5:

Bringing an app together using view models

Bringing an app together using view models

The core piece in the MVVM pattern is the view model layer. This brings together all the other parts of an app’s architecture, connecting views to data and the networking infrastructure.

A view model contains the app’s business logic that drives a single app screen

We can finally focus on the core layer of MVVM: the view model layer.

A view model is limited only to the logic that drives a specific screen in an app. In our example, we only need one for the NewsView type, representing the whole News screen.

Our view model needs to perform all the network requests to retrieve the top 10 best stories from Hacker News.

class NewsViewModel: ObservableObject {
	@Published var stories: [Item?] = Array(repeating: nil, count: 10)
	
	func fetchTopStories() {
		let url = URL(string: "https://hacker-news.firebaseio.com/v0/beststories.json")!
		let request = APIRequest(url: url)
		request.perform { [weak self] (ids: [Int]?) -> Void in
			guard let ids = ids?.prefix(10) else { return }
			for (index, id) in ids.enumerated() {
				self?.fetchStory(withID: id) { story in
					self?.stories[index] = story
				}
			}
		}
	}
	
	func fetchStory(withID id: Int, completion: @escaping (Item?) -> Void) {
		let url = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(id).json")!
		let request = APIRequest(url: url)
		request.perform(with: completion)
	}
}

First of all, the NewsViewModel class conforms to the ObservableObject protocol and publishes its stories property using the @Published property wrapper. This is the “binder” part of MVVM we discussed at the beginning of the article.

While the NewsViewModel class contains the logic to retrieve the stories, it does not know anything about the NewsView. This leaves the view model decoupled from its view, making it easier to write unit tests.

I divided the fetching of data into two methods to avoid callback hell. This is the simplest way to achieve that without using a reactive framework like Combine.

The fetchTopStories() method fetches the IDs of the best stories. Then, it calls the fetchStory(withID:completion:) method for each of these. This, in turn, fetches the details of each story.

All these requests happen in parallel, and their callbacks might occur in any order. Appending the stories to the stories array might then change their order.

To avoid complex synchronization code, I first populate the stories array with 10 nil values and replace them with the corresponding story using indexes.

Creating view models in SwiftUI views and triggering events

All we have left is connecting our view to its view model.

struct NewsView: View {
	@StateObject private var model = NewsViewModel()
	
	var body: some View {
		List(model.stories.indices) { index in
			if let story = model.stories[index] {
				Story(position: index + 1, item: story)
			}
		}
		.navigationTitle("News")
		.onAppear(perform: model.fetchTopStories)
	}
}

The NewsView type creates the NewsViewModel instance using the @StateObject property wrapper. Then, it triggers the network requests by calling the fetchTopStories() method in its .onAppear modifier.

The @ObservedObject wrapper would be a mistake. The view would recreate its view model every at every refresh of the view hierarchy, with the effect of losing the callbacks of all the network requests in progress.

We could also use the @EnvironmentObject wrapper in this specific case, but that’s not a common practice. View models often need to be initialized with particular values, which is only possible in a view’s initializer. 

The @EnvironmentObject is more appropriate for shared instances of global objects. The ones that are usually called controllers in the MVC pattern.

To complete our app, we only need to add a navigation view to our app’s entry point.

HackerNewsApp: App {
	var body: some Scene {
		WindowGroup {
			NavigationView {
				NewsView()
			}
		}
	}
}

The running Hacker News app

Conclusions

The MVVM pattern provides a useful architectural idea: the views in an iOS app often need dedicated objects. These are the view models of MVVM.

In this article, we have seen one of the most common uses for view models: performing a sequence of network requests to fetch data for a single view.

We also saw how to solve many of the problems of sticking to the pattern definition too strictly. Making formatting and networking code reusable simplifies all the view models in an app.

There is only so much this article can cover about an app’s full architecture. As I mentioned, complex apps often need global objects.

The MVVM pattern alone is too limited. Trying to shoehorn all your code into view models is only going to create problems.

There are also some problems that a strict approach to MVVM does not address. For example, why does the NavigationView in the example above go into the main app structure? And what happens in apps with tricky navigation?

Moreover, adding objects to views breaks Xcode previews. After adding the NewsViewModel instance to the NewsView, you will notice that its preview becomes empty.

To solve all these problems, we need an extra layer, which I call the root layer. To know more about it, get my free guide below.

Architecting SwiftUI apps with MVC and MVVM

It’s easy to make an app by throwing some code together. But without best practices and robust architecture, you soon end up with unmanageable spaghetti code. In this guide I'll show you how to properly structure SwiftUI apps.

GET THE FREE BOOK NOW

30 thoughts on “MVVM in iOS with SwiftUI (Detailed Example + Pitfalls)”

  1. Thank You. Good article.
    Can you give advice? How code will look if flight will change at another place and FlightCardView should update data? Who will observed changes?
    Maybe do you have a article for this question? :)

    Reply
  2. Thank you for the article.

    One thing I’m still confused about in your example, is FlightCardView a model or a view controller? What is confusing me is the fact that the view outlets are contained within the FlightCardView class. I can see them all as a struct within FlightCardView but FlightCarView also appears to be changing the view when it places string data in the text properties outlets. In your diagram you show the view reading data from the view mall but it seems like the view model is pushing data into the view. Could you clarify this?

    Reply
  3. Thanks for this amazing article, Matteo!
    I’m curious about one thing: in the example project, your View owns the ViewModel. I thought the MVVM architecture said your ViewController should owns it instead.
    Greetings.

    Reply
  4. What are your thoughts on UIKit independence in ViewModels? If a view has a UIImageView, should the ViewModel have a UIImage property? a Data property? Probably not a string url, because then networking code has slipped into your view / view model layer.

    Reply
  5. Thanks, this was really helpful. I found myself making several of the errors mentioned here. I have a question: under this pragmatic approach, how would you update views after a model change? In this code example, let’s say you receive a push notification about flight departure time change, how would you go from updating the model up to the view that is displaying it?

    Reply
  6. This approach works well for simple views like labels, but how do you apply this methodology when you have more complex/dynamic views, such as UITextFields? You need to send along a delegate. I suppose you could have the view controller be the delegate and pass a reference through via the view model, but that starts to get messy, especially if there are multiple text fields that you need to distinguish between.

    Reply
    • One of the points of this approach is indeed to avoid having the view model as a delegate, or respond to any other kind of message or notification.

      What you propose depends on what you are trying to accomplish.

      For example, if you need to respond to delegate messages from some control just to make layout adjustments in the UI, you should create a custom view class which then acts as a delegate.

      UI is the domain of views. If there is no other consequence, there is no point in propagating it to the view controller layer.

      In other cases though it might be necessary to have the view controller as a delegate. If that starts to become messy, it means you probably have to create a seaprate model controller. A very comon case is a data source for a table view. A good practice is to always create a separate model controller for that.

      I show that in this article: http://matteomanferdini.com/uitableview/

      Reply
  7. Very nice article, thank you. Now suppose I have a UILabel of which the font color changes with the value it is showing. Maybe indicating the flight delay time: for instance, no delay: green, < 30 min: orange, > 30 min: red. Where would the logic to determine the color go, in the view or view-model?

    Reply
    • Good question.

      In general, keeping logic in a structure keeps it more testable. It’s easier to write a unit test for a structure, than it is to write it for a view.

      So I would put this logic in the view model. It also makes sense form the point of view of formatting data: you are turning a time interval into a color.

      Finally, the delay logic is app’s logic. The view does not care about how much delay is in any category. It just cares about the category.

      Reply
  8. Very good!!! Thank you for sharing. A doubt, in case we have a registration view (for insertion of information), how the view model would behave? The view model would be responsible for the transformation the view to the model? We would have:

    private extension FlightInfoViewController {
    func convertViewModelToModel() -> Flight {
    viewModel.airportSymbols = airportSymbolsLabel.text
    viewModel.departureDay = departureDayLabel.text
    viewModel.departureAirport = departureAirportLabel.text


    }
    }

    And this model would be used in the model controller to persist the data. Is this approach correct?

    Reply
    • Yes, you would use a view model to make the reverse transformation as well.

      In fact, you should use the same view model. Chances are you can even reuse some of the code.

      Just add another initializer to the ViewModel struct, which takes the formatted values coming from the view and transforms them back into data you can use in the model controllers of your app.

      You send this back to the view controller using delegation in your view, like UITextFieldDelegate does. Then the view controller propagates it to model controllers.

      Reply
  9. Who is responsible for @IBActions and who should act as a delegate for the subviews in the custom view? For instance, I have a form with a submit button which should only be enabled when all input fields are valid. In the MVC approach, the VC as a UITextField delegate would validate the input, enable/disable the button and act when the button is tapped. But who’s responsibility is this in your MVVM approach? Since the VM “does not receive callbacks and notifications” and “does not update views”, that leaves the VC and the custom view?

    Reply
  10. Hi, thank you for a grate article! I really love your perception – MVVM pattern is an extension of the MVC pattern. Also, I have few questions. According to your comment – ‘Validation happens in the view controller and attached model controllers, but only for the data itself.’ – you means that it belongs to view controller or to model controller. For example, I’m talking about validation for input text field – password.
    As ViewModel responsible to format the presented ‘output’ – data for View, if so it sounds to me logical that ViewModel also should to perform a validation on incoming ‘input’ – data of the Views.
    1. What do you think about it. Where is the right place to put a validations, can you please explain it in more details where to put it and why it should be there.
    2. On the other hand, in case of Login screen when I have a View with two empty input fields email and password and I implemented a validations in the Controller, it seems to me that I don’t need a ViewModel layer at all. Am I right?

    P.S. I was looking for ultimate solution for validations and I found another inspiring article ‘Mixins and Traits in Swift 2.0 ‘ article by @mhollemans, that proposes to implement validations using protocol extensions. https://goo.gl/kAK4av

    Reply
    • It depends what you mean by validation.

      If you mean transforming formatted data on the screen to the model of your app, yes, that would go in the model.

      There is a similar question in another comment, check the gist I made in reply: http://matteomanferdini.com/mvvm-pattern-ios-swift/#comment-3797462443

      If validation means includes business logic, instead, that would not go in a view model, or you would spread it around your app and would have to duplicate it in other places.

      In general, my approach to view models is to remove from view controller knowledge about the UI. Keep in ming that you don’t need always a view model. If data can be displayed as it is, then you would have no view model at all, as you said.

      I personally don’t like design patterns that force you to implement types that are not needed.

      Regrading the article you linked and the bigger picture: validation should *happen* in the view controller, but that does not mean that the view controller should contain that code.

      In fact, I would also put in separate objects like the validators in the article you linked. The view controller should only dispatch execution between views, view models, and other objects.

      Reply
  11. Hi, big thanks for really great article. I have one question: If our views always owns it’s View Models, maybe it will be good to implement some protocol for this first like that:
    protocol ModellableView {
    associatedtype VModel
    var model: VModel? {get set}
    }
    and implement our custom views like that:
    class UserView: UIView, ModellableView {
    typealias VModel = ViewModel
    ….
    var model: VModel? {
    didSet {
    ……
    }
    }
    }
    What benefits we’ll get from that implementation or this is not good aproach?

    Reply
    • That would not produce any benefit. In the end, the view model of a view is a specific type and there is no real reason to abstract it.

      Your protocol only expresses that the view has a viewModel property, but that the compiler knows anyway, so you just get some more code.

      This king of approach (or in genera, abstraction) can be useful when you need to share code between types. But in this case, you added a few lines without reducing any other code in the app.

      Reply
  12. Hi Matteo, thank you so much for the great articles you share. I have a question about how would you hold some external functionality for models, for example each model have data representing an audio player, inside the model I would store the name and progress for example but where would you store the player instance itself so you can call functions on it? Is it something the model should be aware of? Or maybe a model controller? If you have reference to material deals with this question it would be wonderful. Thanks so much!

    Reply
    • This is hard to anwer, since I would have to see the UI of the app and its business logic.

      When you talk about audio player, it’s not clear to me why you would have more than one.

      If your all plays audio files, you probably have a list of files (songs, podcast episodes, etc). For each one you might keep track of the listening position, to resume playback at the same place. That’s part of the model of the app, so these would be all structs. The “player” is the screen with controls to start and stop.

      You might instad be writing a multitrack app, like a song mixing software or the like, where you see on the screen multiple tracks which can all play at the same time.

      This would be indeed more complicated. The view controller would hold a list of reference to audio playing objects, which, in turn, play different tracks. You could consider these to be model controllers, if we want to use strict definitions.

      But each player on the screen can be seen as a single view (probably a cell in a table view), with a view model containing all the data it needs. You would configure each view model using both the state of each single player and the properties of each audio files.

      Reply
  13. How would the MVVM pattern work with Core Data in SwiftUI? For example I want my VM to fetch data from the database or from the API if there is no data persisted. I know that with @FetchRequest you can fetch data directly from the view, how would that work in the MVVM pattern?

    Reply
    • That depends a bit on the structure of your app, but in general, you should have a global controller that handles Core Data that you share as an environment object, so you don’t replicate that code. Then, single view models can interact with this controller.

      Reply
  14. Thank you for a great post! It would be great if you could include information about how to use CoreData with SwiftUI in this architecture. Should CoreData be abstracted out into its own layer or should you take advantage of the options to integrate it directly into the view with FetchRequest and FetchedResults.

    Reply
    • Core Data really deserves an article on its own.

      In short, yes, I would use the same architecture. That should not depend on the specific implementation details.

      The unfortunate compromise of Core Data is that everything is an object. So you need to be more careful to avoid Core Data code leaking into all parts of your apps. But, in the end, SwiftUI bindings are references too, so it makes not much difference.

      Regarding @FetchedRequest, it is convenient but makes me also raise an eyebrow. I rarely go against the platform, but I haven’t carefully considered this yet. On the surface, though, I can say that using it in my root views approach is definitely going to limit the eventual architectural problems that might create.

      Reply

Leave a Comment