1

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.

4
  • You should not use @ObservedObject var data = SharedData() in AnotherView. Did you mean to write @StateObject var data = SharedData() instead (same as in CounterView)? Commented Nov 12, 2024 at 14:31
  • Thank you for the suggestion! I thought @StateObject would be for when I want a view to "own" an instance of an ObservableObject, while @ObservedObject would allow a view to observe changes without creating a new instance. My intention with AnotherView was to simply observe any changes made in CounterView, but it seems that creating a new instance of SharedData in AnotherView is what's causing the unexpected updates. Would passing the SharedData instance from ParentView and using @ObservedObject in both CounterView and AnotherView work better in this scenario? Commented Nov 12, 2024 at 14:37
  • Oh I misunderstood your intention then. The answer by ITGuy is what you need. Commented Nov 12, 2024 at 15:02
  • The point of Swift and SwiftUI is avoid shared mutable state, it might not go well trying to go against that. You would be better off learning structs, let and State/Binding. Commented Nov 12, 2024 at 18:00

1 Answer 1

1

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)")
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.