Search code examples
swiftuicombineobservableobject

SwiftUI: Must an ObservableObject be passed into a View as an EnvironmentObject?


If I create an ObservableObject with a @Published property and inject it into a SwifUI view with .environmentObject(), the view responds to changes in the ObservableObject as expected.

class CounterStore: ObservableObject {
    @Published private(set) var counter = 0
    func increment() {
        counter += 1
    }
}

struct ContentView: View {
    @EnvironmentObject var store: CounterStore

    var body: some View {
        VStack {
            Text("Count: \(store.counter)")
            Button(action: { store.increment() }) {
                Text("Increment")
            }
        }
    }
}

Tapping on "Increment" will increase the count.

However, if I don't use the EnvironmentObject and instead pass the store instance into the view, the compiler does not complain, the store method increment() is called when the button is tapped, but the count in the View does not update.

struct ContentViewWithStoreAsParameter: View {
    var store: CounterStore

    var body: some View {
        VStack {
            Text("Count: \(store.counter) (DOES NOT UPDATE)")
            Button(action: { store.increment() }) {
                Text("Increment")
            }
        }
    }
}

Here's how I'm calling both Views:

@main
struct testApp: App {
    var store = CounterStore()
    
    var body: some Scene {
        WindowGroup {
            VStack {
                ContentView().environmentObject(store) // works
                ContentViewWithStoreAsParameter(store: store) // broken
            }
        }
    }
}

Is there a way to pass an ObservableObject into a View as a parameter? (Or what magic is .environmentalObject() doing behind the scenes?)


Solution

  • It should be observed somehow, so next works

    struct ContentViewWithStoreAsParameter: View {
        @ObservedObject var store: CounterStore
    //...