SwiftUI中模型变更无法在子视图同步显示的原因排查
SwiftUI视图不更新问题解决
问题代码
struct ContentView: View { @StateObject var cntrl=ContentViewController2() var body: some View { VStack { ItemsDisplay(items: cntrl.model.items, handler: cntrl.updateItem) } } } struct ItemsDisplay:View { let items:[SubItem] let handler:(SubItem)->Void init(items: [SubItem], handler: @escaping (SubItem) -> Void) { self.items = items self.handler = handler print("init of ItemsDisplay") for item in items { print("\(item.id) \(item.value)") } } var body: some View { VStack { ForEach(items, id: \.id) {item in ItemDisplay(item: item, handler: handler) } } } } struct ItemDisplay:View { let item:SubItem let handler:(SubItem)->Void init(item: SubItem, handler: @escaping (SubItem) -> Void) { self.item = item self.handler = handler print("init of ItemDisplay: \(item.value)") } var body: some View { VStack { Text("\(item.id)") Text("\(item.value)") } .padding(.all) .border(.black, width:5) .onTapGesture { print("tapped") handler(item) } } } struct SubItem:Equatable { static func == (lhs: SubItem, rhs: SubItem) -> Bool { lhs.id == rhs.id } let id:String var value:Int mutating func changeValue(_ newval:Int) { print("changing value to: \(newval)") value=newval } } struct ContentViewModel2 { var items:[SubItem] mutating func updateItem(_ item:SubItem) { if let i=items.firstIndex(of: item) { let newValue=item.value+1 print("newValue for item: \(item.id) should be: \(newValue)") items[i].changeValue(newValue) print("newValue of item: \(items[i].id) is: \(items[i].value)") } } } class ContentViewController2:ObservableObject { @Published var model:ContentViewModel2 init() { model=ContentViewModel2(items: [SubItem(id: "box1", value: 1), SubItem(id: "box10", value: 10)]) } func updateItem(_ item:SubItem) { if let i=model.items.firstIndex(of: item) { print("updating: \(item.id)") model.updateItem(item) } // objectWillChange.send() } }
问题描述
代码实现了包含两个SubItem盒子的界面,点击盒子应让对应数字加1。目前模型中的值已正确更新,但视图无法显示最新值。控制台输出显示ItemsDisplay的init方法已拿到更新后的items,但body方法未触发刷新。
点击box1时控制台输出:
tapped updating: box1 newValue for item: box1 should be: 2 changing value to: 2 newValue of item: box1 is: 2 init of ItemsDisplay box1 2 box10 10
问题原因
@Published仅在整个被观察对象实例替换或数组整体替换时才会发送更新通知。当前代码只是修改了数组中元素的属性,model本身的内存地址未发生变化,@Published不会触发视图刷新。
修复方案
方案一:替换整个数组
修改ContentViewModel2的updateItem方法,创建新数组替换原数组,让@Published检测到变化:
mutating func updateItem(_ item:SubItem) { if let i=items.firstIndex(of: item) { var newItems = items let newValue=item.value+1 print("newValue for item: \(item.id) should be: \(newValue)") newItems[i].changeValue(newValue) items = newItems // 替换整个数组触发更新 print("newValue of item: \(items[i].id) is: \(items[i].value)") } }
方案二:手动发送更新通知
在ContentViewController2的updateItem方法中手动调用objectWillChange.send(),强制通知视图刷新:
func updateItem(_ item:SubItem) { if let i=model.items.firstIndex(of: item) { print("updating: \(item.id)") model.updateItem(item) objectWillChange.send() // 手动发送更新信号 } }
补充优化
让SubItem遵守Identifiable协议,简化ForEach写法:
struct SubItem:Equatable, Identifiable { let id:String var value:Int static func == (lhs: SubItem, rhs: SubItem) -> Bool { lhs.id == rhs.id } mutating func changeValue(_ newval:Int) { print("changing value to: \(newval)") value=newval } }
此时ItemsDisplay中的ForEach可简化为:
ForEach(items) { item in ItemDisplay(item: item, handler: handler) }
内容的提问来源于stack exchange,提问作者matyasl
相关产品推荐
相关产品推荐

