"I keep hearing about MVVM, but I only half get it..."
"I really should start playing with SwiftUI, but the steps and where to put the code look complicated..."
If that's you, this article will give you the backbone of SwiftUI + MVVM design.
What is MVVM?
MVVM is a code design pattern whose main goal is to separate the Model from the View.
- The Model is the actual substance of what the app does.
- The View is how the app is presented to the user.
A ViewModel translates and relays the changes between them. That way the Model can keep the app's substance clear and single-sourced, and the View can present the Model's state to the user without delay.
Is MVVM required?
MVVM is not required to build an app with SwiftUI, but using it lets you take the declarative approach β "hand a bundle of content changes to the View and let the View figure out how to render it" β which makes writing apps smooth. (By contrast, telling the View "do this, now do that" on every update is the imperative style.)
Let's understand it with the simplest possible example
It's only human to feel "so what on earth do I actually write in a ViewModel?" and "SwiftUI keeps throwing new characters at me like @ObservedObject and @Published β scary." (I felt exactly that.) So I wrote a tiny case study that combines SwiftUI and MVVM.
We'll build the MVVM pattern with the bare-minimum Model, View and ViewModel.
The case study is a switch that toggles between dog πΆ and cat π± when tapped.
A simple switch that toggles on tap:
(The background is green just for clarity.)
A simple MVVM
Writing the Model
The Model of this sample β the substance of this app β is switching between dog and cat.
Model.swift
import Foundation // the Model does NOT import SwiftUI
struct Model {
enum Pet: String { // the case is either dog or cat
case πΆ
case π±
}
var pet: Pet = .πΆ // default is dog
mutating func switchPet() { // toggle dog and cat
if pet == .πΆ {
pet = .π±
} else {
pet = .πΆ
}
}
}
The Model does not import SwiftUI, because it is the app's substance β independent of the UI.
The substance of this app is switching between dog and cat, so the Model consists of a pet variable (dog or cat) and a switchPet function that toggles them. (A struct uses a mutating func to mutate itself.)
That's the entire Model of our app.
Writing the View
The View renders the Model's pet as a Text view, and when the Text view is tapped, it switches the Model's pet.
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("reflect the Model's pet here")
.padding()
.onTapGesture {
// switch the model's pet here
}
}
}
Our View is responsible for displaying the Model's content to the user and for accepting the user's tap.
If we ignore the MVVM pattern, we could hold the Model directly inside the View:
ContentView.swift (holding the Model directly)
import SwiftUI
struct ContentView: View {
@State var model = Model()
// @State lets you change view-state values and reflect them instantly
var body: some View {
Text(model.pet.rawValue)
.padding()
.onTapGesture {
model.switchPet()
}
}
}
Going further, you could of course hold the pet variable and the toggle function in the View itself:
ContentView.swift (holding pet and switchPet directly)
import SwiftUI
struct ContentView: View {
enum Pet: String {
case πΆ
case π±
}
@State var pet: Pet = .πΆ
// @State lets you change view-state values and reflect them instantly
mutating func switchPet() {
if pet == .πΆ {
pet = .π±
} else {
pet = .πΆ
}
}
var body: some View {
Text(pet.rawValue)
.padding()
.onTapGesture {
switchPet()
}
}
}
If this pet variable only represents transient view state, maybe that's fine.
But then, for example, when you have many Views it becomes hard to keep the Model's state single-sourced. MVVM is about not doing that.
Writing the ViewModel
The ViewModel's job is to be the interpreter between View and Model: relaying the user's tap from the View to the Model, and relaying the Model's state back to the View.
ViewModel.swift
import Foundation
import SwiftUI
class ViewModel {
var model: Model = Model() // holds the Model
var pet: String {
return model.pet.rawValue // return the Model's pet as the String the View needs
}
func switchPet() {
model.switchPet() // call the Model's switchPet
}
}
Accessing the ViewModel from the View
The View tells the ViewModel about the user's tap, the ViewModel calls the Model's toggle function, and the View reads the Model's pet value back through the ViewModel.
ContentView.swift
import SwiftUI
struct ContentView: View {
var viewModel = ViewModel()
var body: some View {
ZStack {
Text(viewModel.pet)
.padding()
.onTapGesture {
viewModel.switchPet()
}
}
}
}
Run this and⦠the UI doesn't change.
To check, let's add a print to the Model's switchPet:
Model.swift
mutating func switchPet() {
if pet == .πΆ {
pet = .π±
} else {
pet = .πΆ
}
print(pet)
}
π±
πΆ
π±
πΆ
The Model's pet is toggling, but the UI isn't updating. In terms of the information flow above:
- The View tells the ViewModel about the user's tap (β done)
- The ViewModel calls the Model's toggle function (β done)
-
The View reads the Model's(β this part isn't arriving)petchange back through the ViewModel
In MVVM, the ViewModel publishes Model changes to everyone, and the View subscribes to whatever information it cares about β that's how the View receives Model updates.
This is where SwiftUI's property wrappers come in.
The ViewModel publishes changes, and the View subscribes
ViewModel.swift
import Foundation
import SwiftUI
class ViewModel: ObservableObject { // conform to ObservableObject
@Published var model: Model = Model() // mark it @Published
var pet: String {
return model.pet.rawValue
}
func switchPet() {
model.switchPet()
}
}
By conforming to ObservableObject, the ViewModel becomes observable and can broadcast information to the whole app (to anything willing to observe).
By adding @Published, the ViewModel (an ObservableObject) publishes to everyone the moment this Model changes.
Then the View subscribes to that published change.
ContentView.swift
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel = ViewModel() // add @ObservedObject
var body: some View {
ZStack {
Text(viewModel.pet)
.padding()
.onTapGesture {
viewModel.switchPet()
}
}
}
}
By adding @ObservedObject to the viewModel property, whenever the ObservableObject ViewModel publishes a change, the View can instantly update the relevant UI from its body.
Now the "the View reads the Model's pet value through the ViewModel" part works, and tapping updates the UI.
Dog/cat updating on tap:
This is the simplest possible MVVM.
There's a lot more to it, but I think the basic building blocks of the pattern are all here.
Originally published in Japanese on Qiita. I build apps with Core ML and write about machine learning. GitHub / X



Top comments (0)