SwiftUI List内嵌GoalView未随ObservedObject数据源更新问题求解
问题根因
你遇到的问题核心来自两个逻辑的共同作用:
- 你当前
ForEach的标识规则仅匹配GoalWrapper的id属性,只要id不变,SwiftUI就会判定该位置的视图不需要重新初始化,只会复用已有的GoalView实例,也就不会更新传入的goalWrapper参数。 - 你的
AppConnectivityManager更新goals数组时,是生成了新的GoalWrapper实例,而不是修改原有数组中GoalWrapper实例的goal属性。这就导致已有的GoalView持有的还是旧的GoalWrapper对象,旧对象的goal属性从未更新,自然不会触发视图刷新。而父视图的ForEach因为遍历的是新数组,所以print会输出最新值。
解决方案
以下方案按推荐优先级排序:
方案1(最推荐):修改数据更新逻辑,复用原有GoalWrapper实例
不要在更新数据时直接替换goals数组的元素,而是找到对应id的原有GoalWrapper实例,直接修改它的goal属性:
// 示例更新逻辑,写在AppConnectivityManager内部 func updateGoal(_ newGoal: Goal) { if let index = goals.firstIndex(where: { $0.id == newGoal.id }) { goals[index].goal = newGoal // 直接修改原有实例的@Published属性 } }
因为goal是@Published属性,修改时会自动触发GoalWrapper的objectWillChange信号,你的GoalView已经声明了@ObservedObject var goalWrapper: GoalWrapper,会自动接收信号刷新,不需要修改其他业务代码。
方案2:调整ForEach的视图刷新判定逻辑
如果你不想修改原有数据更新逻辑,就需要让SwiftUI知道即使id相同,元素内容也发生了变化,需要重新初始化GoalView。最简单的实现是给GoalView添加id修饰符,绑定会变化的业务属性:
// 修改ContentView里的ForEach部分 ForEach(manager.goals, id: \.id) { goal in let _ = print("goal in foreach: \(goal.goal.completitionCount) + title: \(goal.goal.title)") GoalView(goalWrapper:goal) .id("\(goal.id)_\(goal.goal.completitionCount)_\(goal.goal.dailyNotificationCount)") // 只要绑定的属性变化,就会重建视图 }.listRowBackground(Color.clear)
只要拼接的id值发生变化,SwiftUI就会销毁旧的GoalView实例,用新的goalWrapper参数创建新的实例,自然就能拿到最新数据。
方案3:使用Binding传递数据(可选替代方案)
你提到的Binding方案也是可行的,你可以把GoalWrapper的goal属性作为Binding传递给子视图,而不是直接传递整个GoalWrapper实例,这样父视图刷新时Binding会自动更新:
// 修改GoalView的声明 struct GoalView: View { @Binding var goal: Goal // 其余代码不变,把所有goalWrapper.goal替换成goal即可 } // 调用时传递Binding ForEach($manager.goals) { $goal in GoalView(goal: $goal.goal) }
这种方案不需要依赖ObservableObject的监听,完全靠SwiftUI的状态传递机制更新,适合不需要在子视图里操作整个GoalWrapper的场景。
内容的提问来源于stack exchange,提问作者beowulf
相关产品推荐
相关产品推荐

