Categories
iOS macOS Swift SwiftUI

@StateObject and MVVM in SwiftUI

A while ago I wrote about using MVVM in a SwiftUI project. During the WWDC’20 Apple announced @StateObject property wrapper which is a nice addition in the context of MVVM. @StateObject makes sure that only one instance is created per a view structure. This enables us to use @StateObject property wrappers for class type view models which eliminates the need of managing the view model’s lifecycle ourselves.

Comparing @StateObject and @ObservableObject for view model property

The most common flow in MVVM is creating and configuring a view model, which includes injecting dependencies, and then passing it into a view. This creates a question in SwiftUI: how to manage the lifecycle of the view model when view hierarchy renders and view structs are recreated. @StateObject property wrapper is going to solve this question in a nice and concise way. Let’s consider an example code.

import SwiftUI
struct ContentView: View {
@EnvironmentObject var dependencyContainer: DependencyContainer
@StateObject var viewModel = ContentViewModel()
var body: some View {
VStack(alignment: .center) {
VStack(spacing: 32) {
Text(viewModel.refreshTimestamp)
Button(action: viewModel.refresh, label: {
Text("Refresh")
})
}
Spacer()
BottomBarView(viewModel: BottomBarViewModel(entryStore: dependencyContainer.entryStore))
}
}
}
ContentView which has a subview.

ContentView is a simple view which has a view model managing the view state and DependencyContainer used for injecting a dependency to the BottomBarViewModel when it is created. As we can see, ContentView’s view model is managed by @StateObject property wrapper. This means that ContentViewModel is created once although ContentView can be recreated several times. BottomBarView has a little bit more complex setup where the view model requires external dependency managed by the DependencyContainer. Therefore, we’ll need to create the view model with a dependency and then initialize BottomBarView with it. BottomBarView’s view model property is also annotated with @StateObject property wrapper.

import Combine
import SwiftUI
struct BottomBarView: View {
@StateObject var viewModel: BottomBarViewModel
var body: some View {
Text(viewModel.text)
}
}
final class BottomBarViewModel: ObservableObject {
@Published var text: String = ""
private var cancellables = [AnyCancellable]()
private let entryStore: EntryStore
init(entryStore: EntryStore) {
self.entryStore = entryStore
print(self, #function)
cancellables.append(Timer.publish(every: 2, on: .main, in: .default).autoconnect().sink { [weak self] (_) in
self?.text = "Random number: \(Int.random(in: 0..<100))"
})
}
}
BottomBarView and its view model.

Magical aspect here is that when the ContentView’s body is accessed multiple times then BottomBarViewModel is not recreated when the BottomBarView struct is initialized. Exactly what we need – view will manage the lifecycle of its view model. This can be verified by adding a print to view model initializers and logging when ContentView’s body is accessed. Here is example log which compares BottomBarView’s view model property when it is annotated with @StateObject or @ObservableObject. Note how view model is not created multiple times when BottomBarView uses @StateObject.

BottomBarView uses @StateObject for its view model property
SwiftUIStateObject.ContentViewModel init()
1 ContentView.body accessed
SwiftUIStateObject.BottomBarViewModel init(entryStore:)
Triggering ContentView refresh
2 ContentView.body accessed
Triggering ContentView refresh
3 ContentView.body accessed

BottomBarView uses @ObservableObject for its view model property
SwiftUIStateObject.ContentViewModel init()
1 ContentView.body accessed
SwiftUIStateObject.BottomBarViewModel init(entryStore:)
Triggering ContentView refresh
2 ContentView.body accessed
SwiftUIStateObject.BottomBarViewModel init(entryStore:)
Triggering ContentView refresh
3 ContentView.body accessed
SwiftUIStateObject.BottomBarViewModel init(entryStore:)

Summary

WWDC’20 brought us @StateObject which simplifies handling view model’s lifecycle in apps using MVVM design pattern.

If you are looking more information about the MVVM design pattern then please check: MVVM in SwiftUI and MVVM and @dynamicMemberLookup in Swift.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Example

SwiftUIStateObject (GitHub) Xcode 12.0 beta 3

3 replies on “@StateObject and MVVM in SwiftUI”

[…] The view object is responsible for describing the UI and rendering data represented by the view model. We have a simple form for modifying the recipient’s first name, last name, and the street name (for keeping this UI simple I left out all the other postal address related properties). At the bottom of the view we have a text object which just describes the package and a button for adding new items to the package’s contents. Whenever any of the package’s properties change, the view needs to reload. View reload is done through the @StateObject property wrapper (read mode about observing view models in MVVM in SwiftUI and @StateObject and MVVM in SwiftUI). […]

Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s