ObservableObject数组内的布尔值无法触发SwiftUI重渲染
解决SwiftUI嵌套ObservableObject变更不触发UI更新问题
问题描述
在SwiftUI项目中,修改嵌套在GridViewModel中的Collection对象内部属性(如isCollapsed)时,UI不会自动更新;只有修改GridViewModel第一层内容(如添加集合)时,才能触发重渲染。演示代码如下:
import SwiftUI import SwiftData class Collection: ObservableObject, Identifiable, Hashable { static func == (lhs: Collection, rhs: Collection) -> Bool { return lhs.title == rhs.title && lhs.items == rhs.items } func hash(into hasher: inout Hasher) { hasher.combine(title) } var id = UUID() @Published var title : String = "" @Published var items : [String] = [] @Published var isCollapsed: Bool = true init(title: String, items: [String]) { self.title = title self.items = items } } class GridViewModel: ObservableObject { @Published var collections : [Collection] = [] } struct ContentView: View { @StateObject var gridViewModel = GridViewModel() var body: some View { VStack { Text("Toggle Visibility with observed Objects") Button("Add Collection", action: { addCollection() }) ScrollView { ForEach(gridViewModel.collections, id: \.self) { coll in Button("toggle visibility for:", action: { coll.isCollapsed.toggle() // 不触发UI更新 }) Button("edit", action: { changeItem() }) Text(coll.title) if coll.isCollapsed { ForEach(coll.items, id: \.self) { item in Text(item) } } Spacer(minLength: 20) } } } .onAppear { let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"]) let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"]) let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"]) gridViewModel.collections = [collection1, collection2, collection3] } } func addCollection() { let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"]) gridViewModel.collections.append(collectionNew) gridViewModel.objectWillChange.send() // 有效触发更新 } func changeItem() { gridViewModel.collections[0].items[0] = "---edit------edit------edit---" print("done: changed to: \(gridViewModel.collections[0].items[0])") gridViewModel.objectWillChange.send() // 有效触发更新 } } #Preview { var gridViewModel = GridViewModel() return ContentView().environmentObject(gridViewModel) #if os(macOS) .frame(width: 700, height: 500) #endif }
可行解决方案
方案1:拆分子视图,用@ObservedObject监听Collection
SwiftUI仅监听当前视图中标记为@ObservedObject/@StateObject的ObservableObject变更。将单个Collection的UI拆分为独立子视图,在子视图中用@ObservedObject持有Collection,即可感知其内部属性变化并触发更新。
示例代码:
// 新增子视图 struct CollectionView: View { @ObservedObject var collection: Collection var body: some View { VStack(alignment: .leading) { Button("toggle visibility for:", action: { collection.isCollapsed.toggle() }) Button("edit", action: { collection.items[0] = "---edit------edit------edit---" }) Text(collection.title) if collection.isCollapsed { ForEach(collection.items, id: \.self) { item in Text(item) } } Spacer(minLength: 20) } } } // 修改ContentView中的ForEach部分 ScrollView { ForEach(gridViewModel.collections, id: \.self) { coll in CollectionView(collection: coll) } }
方案2:让GridViewModel转发Collection的变更通知
在GridViewModel中订阅每个Collection的objectWillChange事件,当Collection内部属性变更时,主动触发GridViewModel的更新通知,让SwiftUI感知到需要刷新UI。
示例代码:
import Combine // 需要引入Combine框架 class GridViewModel: ObservableObject { @Published var collections : [Collection] = [] private var cancellables = Set<AnyCancellable>() // 存储订阅,防止被提前释放 func addCollection(_ collection: Collection) { collections.append(collection) // 订阅Collection的变更事件,转发给自身的objectWillChange collection.objectWillChange.sink { [weak self] _ in self?.objectWillChange.send() }.store(in: &cancellables) } } // 修改addCollection方法 func addCollection() { let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"]) gridViewModel.addCollection(collectionNew) } // 修改onAppear中的初始化逻辑 .onAppear { let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"]) let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"]) let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"]) gridViewModel.addCollection(collection1) gridViewModel.addCollection(collection2) gridViewModel.addCollection(collection3) }
方案3:将Collection改为值类型(struct)
如果业务逻辑允许,把Collection从ObservableObject改为普通struct。SwiftUI会自动监听值类型的变化——当struct的属性修改时,整个struct会被视为变更,从而触发UI更新。
示例代码:
// 修改Collection为struct struct Collection: Identifiable, Hashable { static func == (lhs: Collection, rhs: Collection) -> Bool { return lhs.title == rhs.title && lhs.items == rhs.items && lhs.id == rhs.id } func hash(into hasher: inout Hasher) { hasher.combine(title) hasher.combine(id) } var id = UUID() var title : String = "" var items : [String] = [] var isCollapsed: Bool = true init(title: String, items: [String]) { self.title = title self.items = items } } // 修改ContentView中的ForEach,使用绑定语法修改值类型属性 ScrollView { ForEach($gridViewModel.collections) { $coll in Button("toggle visibility for:", action: { $coll.isCollapsed.toggle() }) Button("edit", action: { $coll.items[0] = "---edit------edit------edit---" }) Text(coll.title) if coll.isCollapsed { ForEach(coll.items, id: \.self) { item in Text(item) } } Spacer(minLength: 20) } }
方案选择建议
- 若
Collection包含复杂业务逻辑(如异步操作、内部状态管理),优先选择方案1或方案2; - 若业务逻辑简单,方案3更简洁,利用SwiftUI原生的値类型监听机制,减少
ObservableObject的复杂度。
内容的提问来源于stack exchange,提问作者blendstylez
相关产品推荐
相关产品推荐

