@Published 不能按预期使用环境对象和嵌套的 ObservableObejct

@Published doesn't work as expected with enviornment object and nested ObservableObejct

假设我有 UserSettings EnviornmentObject,其中一个属性是 class,问题是当我更改 class 的值时,EnviornmentObject 不会发布这些更改。我明白为什么,但我似乎找不到解决方法。

这里是显示问题的简化代码:

struct TestView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        ZStack {
            Text("test: \(self.settings.ob.val)")

            VStack {
                // This is the only one that works and that makes sense, it changes the entire object
                Button(action: {
                    self.settings.changeOb(to: testOb(val: "1"))
                }) {
                    Text("Change object")
                }

                // From here on nothing works, I tried different ways to change the object value

                Button(action: {
                    self.settings.ob.changeVal(to: "2")
                }) {
                    Text("Change object's val")
                }

                Button(action: {
                    self.settings.changeVal(to: "3")
                }) {
                    Text("Change object's val V2")
                }

                Spacer()
            }
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        return ZStack {
            TestView().environmentObject(UserSettings(ob: testOb("abc")))
        }
    }
}

class testOb: ObservableObject {
    @Published private(set) var val: String

    init(val: String) {
        self.val = val
    }

    func changeVal(to: String) {
        self.val = to
    }
}

class UserSettings: ObservableObject {
    @Published private(set) var ob: testOb

    init(ob: testOb) {
        self.ob = ob
    }

    func changeOb(ob: testOb) {
        self.ob = ob
    }


    func changeVal(to: String) {
        self.ob.val(to: to)
    }
}

我终于能够修复它,问题是 Apple 不支持嵌套的 ObesrvableObjects 以及许多重要的功能(这很可悲,但无论如何),但据我所知,这是他们的议程。

解决方案是每次我的对象发生变化时我都必须手动通知,你拥有的对象越多,这可能会很痛苦。

要通知我们首先需要导入 Combine 这样我们就可以使用 AnyCancellable 负责通知。

这是更新后的代码:

    // FIRST CHANGE
    import Combine

    struct TestView: View {
        @EnvironmentObject var settings: UserSettings

        var body: some View {
            ZStack {
                Text("test: \(self.settings.ob.val)")

                VStack {
                    // This is the only one that works and that makes sense, it changes the entire object
                    Button(action: {
                        self.settings.changeOb(to: testOb(val: "1"))
                    }) {
                        Text("Change object")
                    }

                    // From here on nothing works, I tried different ways to change the object value

                    Button(action: {
                        self.settings.ob.changeVal(to: "2")
                    }) {
                        Text("Change object's val")
                    }

                    Button(action: {
                        self.settings.changeVal(to: "3")
                    }) {
                        Text("Change object's val V2")
                    }

                    Spacer()
                }
            }
        }
    }

    struct TestView_Previews: PreviewProvider {
        static var previews: some View {
            return ZStack {
                TestView().environmentObject(UserSettings(ob: testOb("abc")))
            }
        }
    }

    class testOb: ObservableObject {
        @Published private(set) var val: String

        init(val: String) {
            self.val = val
        }

        func changeVal(to: String) {
            self.val = to
        }
    }

    class UserSettings: ObservableObject {
        @Published private(set) var ob: testOb

        // SECOND CHANGE, this is responsible to notify on changes
        var anyCancellable: AnyCancellable? = nil

        init(ob: testOb) {
            self.ob = ob

            // THIRD CHANGE, initialize our notifier
            anyCancellable = self.ob.objectWillChange.sink { (_) in
               self.objectWillChange.send()
            }
        }

        func changeOb(ob: testOb) {
            self.ob = ob
        }


        func changeVal(to: String) {
            self.ob.val(to: to)
        }
    }

尝试这样的事情:

class UserSettings: ObservableObject {
    @Published var ob: testOb{
        willSet{
            observer.cancel()
        }
        didSet{
            observer = ob.objectWillChange.sink(){self.objectWillChange.send()}
        }
    }
    var observer: AnyCancellable!

    init(ob: testOb) {
        self.ob = ob
        self.observer = nil
        self.observer = ob.objectWillChange.sink(){self.objectWillChange.send()}
    }
    func sendChange(){

    }
    func changeOb(ob: testOb) {
        self.ob = ob
    }

    func changeVal(to: String) {
        self.ob.changeVal(to: to)
    }
    deinit {
        observer.cancel()
    }
}

@Published是发送通知,不是监听

或者您可以将弱 link 存储到子对象中的父对象,并在子对象的 willSet{} 中调用 parent.objectWillChange.send()