Reputation: 21
I’m working on an iOS app in SwiftUI where I have a shared ObservableObject that I want to pass between multiple views. The object is meant to keep track of some shared app state, like user settings or session data.
In one of my views, I use @StateObject to create an instance of this object. However, I noticed that any changes I make to the ObservableObject in another view cause my original view to update unexpectedly. It’s as if the object is being observed in multiple places, even though I thought @StateObject would keep it isolated in the view where it’s declared.
Here’s a simplified version of my code:
import SwiftUI
class SharedData: ObservableObject {
@Published var counter: Int = 0
}
struct ParentView: View {
var body: some View {
VStack {
CounterView()
AnotherView()
}
}
}
struct CounterView: View {
@StateObject private var data = SharedData()
var body: some View {
VStack {
Text("Counter: \(data.counter)")
Button("Increment Counter") {
data.counter += 1
}
}
}
}
struct AnotherView: View {
@ObservedObject var data = SharedData() // <- Issue?
var body: some View {
Text("Another View Counter: \(data.counter)")
}
}
When I increment the counter in CounterView, AnotherView also reflects the changes in data.counter. I don’t understand why AnotherView is updating when CounterView increments data.counter, as I thought each view would manage its own instance.
Upvotes: 1
Views: 96
Reputation: 715
Properties that are provided with the ObservedObject
property wrapper should never have a default or initial value.
Apple is also very clear about this in the documentation:
Don’t specify a default or initial value for the observed object. Use the attribute only for a property that acts as an input for a view, as in the above example.
ObservedObject
wrapped properties should therefore always be injected into the view as a dependency and never be created in the view itself.
The background to this is that otherwise, every time the view is recreated, a new instance of your
ObservableObject
conforming model instance is created. This is exactly what you don't want if you want to share the state between views / view instances.
So in your code you could do the following if you want to use one instance of SharedData
for both views:
struct ParentView: View {
@StateObject private var data = SharedData()
var body: some View {
VStack {
CounterView(data: data)
AnotherView(data: data)
}
}
}
struct CounterView: View {
@ObservedObject var data: SharedData
var body: some View {
VStack {
Text("Counter: \(data.counter)")
Button("Increment Counter") {
data.counter += 1
}
}
}
}
struct AnotherView: View {
@ObservedObject var data: SharedData
var body: some View {
Text("Another View Counter: \(data.counter)")
}
}
Or if you want to use a separate instance for each view:
struct ParentView: View {
var body: some View {
VStack {
CounterView()
AnotherView()
}
}
}
struct CounterView: View {
@StateObject private var data = SharedData()
var body: some View {
VStack {
Text("Counter: \(data.counter)")
Button("Increment Counter") {
data.counter += 1
}
}
}
}
struct AnotherView: View {
@StateObject private var data = SharedData()
var body: some View {
Text("Another View Counter: \(data.counter)")
}
}
Upvotes: 1