SwiftUI:必须将 ObservableObject 作为 EnvironmentObject 传递到 View 中吗?

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

如果我使用 @Published 属性 创建一个 ObservableObject 并使用 .environmentObject() 将其注入到 SwifUI 视图中,该视图会按预期响应 ObservableObject 中的更改.

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")
            }
        }
    }
}

点击“增量”将增加计数。

但是,如果我不使用 EnvironmentObject 而是将存储实例传递到视图中,编译器不会报错,当点击按钮时会调用存储方法 increment() ,但 View 中的计数 不会 更新。

struct ContentViewWithStoreAsParameter: View {
    var store: CounterStore

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

以下是我调用两个视图的方式:

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

有没有办法将 ObservableObject 作为参数传递到视图中? (或者 .environmentalObject() 在幕后施展了什么魔法?)

不知何故应该观察到,所以下一个作品

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

您可以轻松将您的店铺传承为 @StateObject:

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

struct ContentViewWithStoreAsParameter: View {
    @StateObject var store: CounterStore

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

但是,商店通常只对需要它的视图可用,为什么这个解决方案在这种情况下最有意义:

struct ContentView: View {
    @StateObject var store = CounterStore()

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