首页 > 解决方案 > 检测 ObservableObject 的 ObservableObject 数组中的变化

问题描述

以下代码是从我在一个更大的应用程序中面临的问题中提取的最小可重现示例。使用以下代码,我正在尝试:

  1. 拥有一个Container带有可观察对象的可观察数组的视图 ( )
  2. 通过数组中的索引访问对象之一
  3. 更改观察对象实例的属性
  4. 通过使用 changed 属性重绘数组,让带有传递数据的子视图 ( ObjectVisualizer) 反映对对象所做的更改

问题是对任何对象的任何更改都不会反映。如何让它发挥作用?

class MyObservedObject: Hashable, Equatable, ObservableObject {
    @Published var text: String
    
    init(_ text: String) {
        self.text = text
    }
    
    static func == (lhs: MyObservedObject, rhs: MyObservedObject) -> Bool {
        return lhs.text == rhs.text
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.text)
    }
    
    static func newSet() -> [MyObservedObject] {
        return [
            MyObservedObject("a"),
            MyObservedObject("b"),
            MyObservedObject("c"),
        ]
    }
}

class ContainerData: ObservableObject {
    @Published var objects = MyObservedObject.newSet()
}

struct Container: View {
    @ObservedObject private var data = ContainerData()
    
    var body: some View {
        ObjectVisualizer(data.objects)
        
        Button("Click me") {
            self.data.objects[2].text = "Changed"
        }
    }
}
    
struct ObjectVisualizer: View {
    private var objects: [MyObservedObject]
    
    init(_ objects: [MyObservedObject]) {
        self.objects = objects
    }
    
    var body: some View {
        ForEach(self.objects, id: \.self) {obj in
            Text(obj.text)
        }
    }
}
    
struct ContentView: View {
    var body: some View {
        Container()
    }
}

标签: swiftswiftui

解决方案


预先警告:

如果您的模型是一个结构而不是 ObservableObject,这将更容易——您基本上可以免费获得所有这些行为。但是,我假设您的应用程序不能以这种方式构建是有原因的。话虽如此,尝试使用 ObservableObjects 数组来做这件事是一场艰苦的战斗,我认为这是在与 SwiftUI 的设计工作方式作斗争。但是,该解决方案似乎确实起作用。



import Combine

class MyObservedObject: Hashable, Equatable, ObservableObject {
    let id = UUID()
    @Published var text: String
    
    init(_ text: String) {
        self.text = text
    }
    
    static func == (lhs: MyObservedObject, rhs: MyObservedObject) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.id)
    }
    
    static func newSet() -> [MyObservedObject] {
        return [
            MyObservedObject("a"),
            MyObservedObject("b"),
            MyObservedObject("c"),
        ]
    }
}

class ContainerData: ObservableObject {
    @Published var objects = MyObservedObject.newSet()
}

struct Container: View {
    @ObservedObject private var data = ContainerData()
    
    var body: some View {
        VStack {
            ObjectVisualizer(data.objects)
            
            Button("Click me") {
                self.data.objects[2].text = "Changed"
                data.objectWillChange.send()
            }
        }
    }
}

class MyObservedObjectWrapper : ObservableObject {
    var objects: [MyObservedObject]
    
    init(objects: [MyObservedObject]) {
        self.objects = objects
    }
}

struct ObjectVisualizer: View {
    @ObservedObject var wrapper : MyObservedObjectWrapper
    
    init(_ objects: [MyObservedObject]) {
        self.wrapper = MyObservedObjectWrapper(objects: objects)
    }
    
    var body: some View {
        ForEach(wrapper.objects, id: \.id) {obj in
            Text(obj.text)
        }
    }
}

struct ContentView: View {
    var body: some View {
        Container()
    }
}

发生了什么:

  1. MyObservedObject具有唯一的 ID,而不是依赖于text字段。

  2. 当按下“点击我”按钮时,它必须手动调用objectWillChange.send(),否则 ObservableObject 不知道它正在被更新(因为您正在修改引用类型数组而不是值类型。

  3. ObjectVisualizer视图不想只使用传入的引用类型数组来重新渲染。所以,我将它们放在一个包装对象中。这似乎触发了ForEach


做这样的事情是否值得重构以拥有一个结构模型?在我看来,可能不是,但当然,我对您的特定情况如何运作没有更多的了解。


推荐阅读