@EnvironmentObject更新Realm数据时子视图未同步刷新问题
问题根因
子视图不刷新的核心原因是你错误使用了@State属性包装器接收外部传入的数据源:
@State是为当前视图私有本地状态设计的,仅会在视图首次初始化时读取一次传入的初始值,后续父视图重新渲染、传入新值时,子视图本地的@State不会被新值覆盖,会一直保留初始化时拿到的旧数据。- 新增/删除场次能正常刷新,是因为这类操作会改变顶层
schedule数组的元素数量,SwiftUI会直接新建/销毁对应的SessionRow实例,新实例初始化时可以拿到最新数据;但场次内部Slot的增删改不会改变Session总数,已存在的SessionRow不会被重建,它本地持有的@State var session: [Slot]始终是初始化时的旧数组,自然无法触发内部SlotRow的更新,SlotRow里用@State接收slot数据也存在完全相同的问题。 - 你加的
refreshToggle、手动强制切主线程的操作无效,本质是因为这些变更没有触达已存在子视图内部持有的旧@State值。
修复方案
- 所有子视图中用来接收父视图传递数据的属性,移除
@State包装,改成普通的let存储属性即可;只有视图本地私有、不需要外部同步的UI状态(比如本地弹窗显隐、临时输入内容)才适合用@State。 - 替换索引遍历的ForEach写法:不要用
0..<count+下标的方式遍历数组,这种写法靠索引做视图标识,数组元素内容变更时SwiftUI很难精准识别差异,建议让模型遵循Identifiable协议,直接遍历元素,用元素自身的id做视图标识。 - 删掉冗余的强制刷新逻辑:Realm本身的数据变更通知会在主线程投递,不需要手动包裹
DispatchQueue.main.async、切换toggle这类hack操作。
修正后的核心代码
struct SessionRow: View { // 移除外部传入参数的@State包装 let sessionNumber: Int let session: [Slot] var delete: () -> Void var addSlot: () -> Void var body: some View { VStack{ HStack{ Text("Session \(sessionNumber + 1)") Button { delete() } label: { Label("", systemImage: "trash") } .buttonStyle(PlainButtonStyle()) Button { addSlot() } label: { Label("", systemImage: "plus") } .buttonStyle(PlainButtonStyle()) } // 用元素id做标识,避免索引遍历的刷新问题 ForEach(Array(session.enumerated()), id: \.element.id) { slotNumber, slot in SlotRow(slot: slot, ownPosition: [sessionNumber, slotNumber]) } } .onAppear{ print("Session Number: \(sessionNumber) loaded.") } } }
struct SlotRow: View { // 同样移除外部传入参数的@State包装 let slot: Slot let ownPosition: [Int] var body: some View { VStack{ HStack{ Text("Own Position: [\(ownPosition[0]),\(ownPosition[1])]") } } .padding() .frame(height: 80.0) .onAppear{ print("Slot \(ownPosition) loaded.") } } func deleteIntervalFromSlot() { // 后续需要操作Realm时,直接通过@EnvironmentObject引入RealmManager调用方法即可 } }
// 顶层操作方法移除冗余逻辑 func addSlot(sessionNumber: Int){ realmManager.addSlot(sessionNumber: sessionNumber) schedule = realmManager.schedule } func deleteSession(at offsets: IndexSet){ realmManager.deleteSession(sessionNumber: Int(offsets.first ?? 0)) schedule = realmManager.schedule }
额外说明
如果Slot是Realm生成的Object引用类型,只要顶层的schedule属性被@State/@StateObject/@ObservedObject正确包装,数据变更时顶层属性更新后,会自动把最新值传递给所有子视图,子视图用普通属性接收即可自动触发刷新,不需要额外做强制刷新操作。
内容的提问来源于stack exchange,提问作者stevednick
相关产品推荐
相关产品推荐

