Shared ViewModel Across Views
TODO: collapasable, copyable, download, etc
I want to use the same ViewModel across several views. What for view model fooViewModel
and views BarView
, BazView
, can you show me a minimal simple codebase in swift Ui that does this properly without running into Accessing StateObject's object without being installed on a View. This will create a new instance each time.
runtime issues?
Spatial computing goes even further with visionOS
updates that bring enhanced support for
volumetric apps, powerful new enterprise APIs, the
new TabletopKit framework, and more.
visionOS Highlights
TabletopKit Create collaborative experiences centered around a table. This new framework handles the manipulation of cards and pieces, establishing placement and layout, and defining game boards.
New enterprise APIs provide access to spatial barcode scanning, the Apple Neural Engine, object tracking parameters, and more.
visionOS for enterprise
Volumetric APIs • Create apps that take full advantage of depth and space — and can
run side by side with other apps in the Shared Space.
• Resize volumes by using the SwiftUI scene
modifier windowResizability.
Decide if you want the user’s hands to appear in front of or behind your digital content.
Inputs
Getting started? Explore the visionOS Pathway on developer.apple.com
• Detect planes in all orientations.
• Allow anchoring objects on surfaces in your surroundings.
• Use Room Anchors to consider the user’s surroundings on a
per-room basis.
• Attach content to individual objects around the user with the new
Object Tracking API for visionOS.
Updates to scene understanding
To share a ViewModel
across several views in SwiftUI without running into the issue of Accessing StateObject's object without being installed on a View
, you should use the @ObservedObject
and @StateObject
property wrappers correctly. The @StateObject
should be used in the parent view where the ViewModel
is created, and the @ObservedObject
should be used in the child views that share the ViewModel
.
Here is a minimal example illustrating how to do this:
ViewModel
import SwiftUI
import Combine
class FooViewModel: ObservableObject {
@Published var text: String = "Hello, world!"
}
ParentView
import SwiftUI
struct ParentView: View {
@StateObject private var fooViewModel = FooViewModel()
var body: some View {
VStack {
BarView(viewModel: fooViewModel)
BazView(viewModel: fooViewModel)
}
}
}
BarView
import SwiftUI
struct BarView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
Text(viewModel.text)
}
}
BazView
import SwiftUI
struct BazView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
TextField("Update text", text: $viewModel.text)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
}
Full Content
Ensure all parts of the code are included within a Swift file:
import SwiftUI
import Combine
class FooViewModel: ObservableObject {
@Published var text: String = "Hello, world!"
}
struct ParentView: View {
@StateObject private var fooViewModel = FooViewModel()
var body: some View {
VStack {
BarView(viewModel: fooViewModel)
BazView(viewModel: fooViewModel)
}
}
}
struct BarView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
Text(viewModel.text)
}
}
struct BazView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
TextField("Update text", text: $viewModel.text)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ParentView()
}
}
}
Explanation:
FooViewModel
is the shared view model class.ParentView
uses@StateObject
to initialize thefooViewModel
. This ensures that theViewModel
is created only once and managed by the SwiftUI framework.BarView
andBazView
use@ObservedObject
to observe changes infooViewModel
. SinceParentView
manages the lifecycle offooViewModel
,BarView
andBazView
will not create new instances offooViewModel
when they access it.
This setup avoids the issue of accessing StateObject
without being installed on a view, ensuring that the ViewModel
instance is shared properly across multiple views.I see how ParentView composes the other views... however I'm working on a RealityKit app. It has a WindowGroup and an ImmersiveSpace. Can you do the same minimal app, but showing how to use fooViewModel
across ParentView
as well as ImmersiveView
?In a RealityKit app that uses both a WindowGroup
and an ImmersiveSpace
, you can still share a ViewModel
across these different views. Here's how you can adapt the previous example to work with WindowGroup
and ImmersiveSpace
.
ViewModel
import SwiftUI
import Combine
class FooViewModel: ObservableObject {
@Published var text: String = "Hello, world!"
}
ParentView
import SwiftUI
struct ParentView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
VStack {
BarView(viewModel: viewModel)
BazView(viewModel: viewModel)
}
}
}
BarView
import SwiftUI
struct BarView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
Text(viewModel.text)
}
}
BazView
import SwiftUI
struct BazView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
TextField("Update text", text: $viewModel.text)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
}
ImmersiveView
import SwiftUI
import RealityKit
import RealityKitContent
struct ImmersiveView: View {
@ObservedObject var viewModel: FooViewModel
var body: some View {
Text(viewModel.text)
.background(Color.black.opacity(0.5))
.padding()
}
}
MyApp (Main Entry Point)
import SwiftUI
@main
struct MyApp: App {
@StateObject private var fooViewModel = FooViewModel()
var body: some Scene {
WindowGroup {
ParentView(viewModel: fooViewModel)
}
ImmersiveSpace {
ImmersiveView(viewModel: fooViewModel)
}
}
}
Explanation:
- ViewModel:
FooViewModel
remains the same, managing shared state. - ParentView: Uses
@ObservedObject
to observe thefooViewModel
. - BarView and BazView: Child views also use
@ObservedObject
to observe the shared state. - ImmersiveView: A new view for the immersive space, also using
@ObservedObject
to observe the shared state. - MyApp:
WindowGroup
is used to create the main window withParentView
.ImmersiveSpace
is used to create the immersive experience withImmersiveView
.- Both
WindowGroup
andImmersiveSpace
share the same instance ofFooViewModel
through@StateObject
.
This structure ensures that fooViewModel
is shared across both the WindowGroup
and the ImmersiveSpace
without creating new instances, thus avoiding the runtime issue of accessing StateObject
without being installed on a View.
Related Chats
Reusable SwiftUI custom view. 0.863
Immersive View Phases 0.835
SwiftUI RealityKit Integration 0.830
MultipeerManager Error Fix 0.826
Handling App Exit 0.807
Preload Assets in visionOS 0.795
MVVM: Prefix with `$` 0.784