SwiftUI ForEach动态内容更新异常求助:数据变动但UI未即时刷新
问题根因
两个场景的核心问题完全一致,都和SwiftUI的视图diff规则、引用类型的特性相关:
- 你定义的所有ViewModel都是引用类型(class),SwiftUI的ForEach对遵守Identifiable协议的元素做diff校验时,只会判断
id是否发生变化,只要id不变就默认元素没有修改,不会触发对应子视图的重绘 - 第二个场景中你自定义的
Equatable实现仅对比id字段,就算其他影响UI的属性发生了变化,SwiftUI也会判断两个实例相等,不会触发视图更新 - 顶部的Text可以正常更新,是因为每次
@Published修饰的数组发布变更时,Text都会直接读取数组最新的元素属性重新计算渲染,不需要走ForEach的元素diff逻辑
解决方案
方案1:将ViewModel改为值类型(推荐)
把TimeCodeViewModel、TimeCodeCellViewModel从class修改为struct,值类型的任何属性变更都会让整个实例的标识发生变化,ForEach可以直接检测到差异,自动触发重绘,不需要额外修改其他逻辑。
方案2:保留class的前提下修改
如果有必须使用class的业务场景,可以选择以下任意一种修改方式:
- 子视图用
@ObservedObject接收ViewModel:让ViewModel遵守ObservableObject协议,所有影响UI的属性加上@Published修饰,子视图直接接收ViewModel实例而非单独传递属性值,属性变更时子视图会自动刷新 - 修改
Equatable实现:把所有影响UI渲染的属性都加入相等判断逻辑,比如TimeCodeCellViewModel的==方法需要同时对比id、isSet、timeCode、date,这样属性变更时SwiftUI可以检测到实例差异 - 强制ForEach刷新:临时验证可以给ForEach加上
.id(数组变量名)修饰符,强制每次数组变化时整个ForEach重新渲染,不过这种方式性能较差,不推荐长期使用
首个场景额外注意点
请确认你sheet的onDismiss绑定的didDismiss方法中,已经调用了timeCodeListVM.fetchAllTimeCodes(),保证CoreData的变更可以同步到ViewModel的数组中。你当前的TimeCodeDetailsCell是单独接收isFavorite等属性值,只要ForEach认为元素没有变更,就不会给Cell传入新的属性值,所以就算数组更新了,Cell的UI也不会变,改成传入整个@ObservedObject修饰的ViewModel实例即可解决。
内容的提问来源于stack exchange,提问作者greatxp117
相关产品推荐
相关产品推荐

