SwiftUI树形结构UI更新问题:嵌套对象修改后UI不刷新
你猜的没错——问题确实出在@Published的工作机制上。@Published只会在它修饰的属性本身的引用发生变化时,才会向订阅它的View发送更新通知。在你的代码里,model.nested的引用始终没变,只是修改了它内部的children数组元素,所以Model0不会发布任何更新,UI自然也就不会刷新。
下面给你几种可行的解决方案,你可以根据自己的需求选择:
方案1:让嵌套类也遵循ObservableObject
这是最推荐的方案,适合需要在嵌套节点中处理业务逻辑的场景,而且能做到精准刷新——只有发生变化的节点对应的View才会更新。
首先修改Nested类,让它遵循ObservableObject,并把需要监听的属性用@Published修饰:
class Nested: Identifiable, ObservableObject { var id: UUID = UUID() @Published var name: String @Published var children:[Nested] init(name: String, children:[Nested]) { self.children = children self.name = name } }
然后修改NestedView,把传入的nested用@ObservedObject修饰,这样每个嵌套节点的变化都会被View监听:
struct NestedView: View { @ObservedObject var nested: Nested var body: some View { VStack { Text(nested.name) ForEach(nested.children) { c in NestedView(nested: c) } } } }
现在你运行代码点击Update按钮,UI就会正常更新了——因为替换children[0]时,nested.children是@Published属性,会触发Nested的更新通知,对应的NestedView就会刷新。
方案2:将嵌套类型改为值类型(Struct)
如果你的嵌套节点不需要类的特性(比如引用共享、继承等),用值类型会更简单,不需要处理多层ObservableObject的订阅。
把Nested改成结构体:
struct Nested: Identifiable { var id: UUID = UUID() var name: String var children:[Nested] init(name: String, children:[Nested]) { self.children = children self.name = name } }
然后修改Model0的update方法——因为值类型是不可变的,你需要创建一个新的nested实例来替换原来的:
func update() { var updatedNested = nested updatedNested.children[0] = Nested(name:"New", children: []) self.nested = updatedNested }
这样,self.nested的引用(值类型的实例)发生了变化,@Published会立刻发送更新通知,UI随之刷新。这种方案的缺点是当树形结构很深时,修改深层节点需要复制整个路径,代码会比较繁琐,大结构可能有性能开销。
方案3:手动发送更新通知(不推荐)
如果你不想修改Nested的结构,可以在修改嵌套内容前手动触发Model0的更新通知:
func update() { // 手动发送更新信号 self.objectWillChange.send() nested.children[0] = Nested(name:"New", children: []) }
这种方法虽然能解决问题,但非常不优雅——你需要在所有修改嵌套结构的地方都调用objectWillChange.send(),很容易遗漏,而且会触发整个UI的刷新,性能不如方案1。
内容的提问来源于stack exchange,提问作者BenJacob

