基于Core Data FetchedResults的视图实时更新问题求助
问题描述
父视图通过Core Data读取数据生成子节点列表,期望数据更新时对应行视图实时刷新,但遇到以下问题:
- 首次尝试用
FetchedResults构建列表并传递@ObservedObject给行视图,仅切换标签页时才更新,无法实时生效。 - 第二次通过
NotificationCenter监听NSManagedObjectContextDidSave,配合@State的refreshID刷新列表,虽能触发重渲染,但.onReceive触发过于频繁,且外设推送消息时会意外关闭用于更新的Sheet视图。
相关代码
NodeList
struct NodeList: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "user.vip", ascending: false), NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) var nodes: FetchedResults<RadioInfoEntity> private var didSave = NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave) @State private var refreshID = UUID() @State private var path: NavigationPath = NavigationPath() var body: some View { NavigationStack (path: $path) { List(nodes, id: \.self) { node in NodeListItem(node: node, connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) .onReceive(self.didSave) { _ in self.refreshID = UUID() print("generated a new UUID") } }.id(refreshID) .onAppear { if self.bleManager.context == nil { self.bleManager.context = context } } } } }
NodeListItem & SheetView
struct NodeListItem: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @ObservedObject var node: RadioInfoEntity @State private var name: String = "" @State private var showingSheetView = false var body: some View { LazyVStack(alignment: .leading) { Text(name) let status = getStatus(status: name) Text(status) Button("Update") { showingSheetView.toggle() } .sheet(isPresented: $showingSheetView) { SheetView(user: node) } }.onAppear(perform: { if let tempName = node.user?.name{ self.name = tempName } }) } } struct SheetView: View { @Environment(\.dismiss) var SheetViewDismiss @ObservedObject var user: RadioInfoEntity @State private var name: String = "" @State private var customName: String = "" @State private var longName: String = "" var connectedUser: User? var body: some View { HStack{ TextField("name", text: $customName) } .onAppear(perform: { self.longName = user.user?.name ?? "" }) Button("Update") { self.user.objectWillChange.send() var u = User() u.name = getUpdatedName(name: name) bleManager.saveUser(config: u, fromUser: connectedUser, toUser: user.user!) SheetViewDismiss() } } } }
BLEManager
class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var context: NSManagedObjectContext? let FROMRADIO_UUID = CBUUID(string: "YOUR_UUID_HERE") var decodedInfo: DecodedInfo! // 需补充解码逻辑 func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { switch characteristic.uuid { case FROMRADIO_UUID: switch decodedInfo.packet.decoded.portnum { case .nodeinfoApp: upsertNodeInfoPacket(packet: decodedInfo.packet, context: context!) } } } }
Core Data 更新逻辑
func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) { let fetchNodeInfoAppRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "RadioInfoEntity") fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) do { let fetchedRadio = try context.fetch(fetchNodeInfoAppRequest) as? [RadioInfoEntity] ?? [] fetchedRadio[0].id = Int64(packet.from) fetchedRadio[0].num = Int64(packet.from) fetchedRadio[0].lastHeard = Date() if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { if nodeInfoMessage.hasUser { fetchedRadio[0].user!.userId = nodeInfoMessage.user.id fetchedRadio[0].user!.num = Int64(nodeInfoMessage.num) fetchedRadio[0].user!.name = nodeInfoMessage.user.name } } do { try context.save() print("💾 Updated NodeInfo from Node Info App Packet For: \(fetchedRadio[0].num)") } catch { context.rollback() let nsError = error as NSError print("💥 Error Saving RadioInfoEntity from NODEINFO_APP \(nsError)") } } catch { print("💥 Error Fetching RadioInfoEntity for NODEINFO_APP") } }
解决方案
1. 修复FetchedResults实时更新失效问题
行视图未实时刷新是因为将node.user?.name存入@State变量,切断了与@ObservedObject的绑定。直接在视图中使用实体属性,让@ObservedObject自动监听变化:
struct NodeListItem: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @ObservedObject var node: RadioInfoEntity @State private var showingSheetView = false var body: some View { LazyVStack(alignment: .leading) { Text(node.user?.name ?? "Unknown") let status = getStatus(status: node.user?.name ?? "") Text(status) Button("Update") { showingSheetView.toggle() } .sheet(isPresented: $showingSheetView) { SheetView(user: node) } } } }
2. 移除冗余的NotificationCenter监听
FetchedResults原生支持Core Data变化监听,删除NodeList中didSave发布者、refreshID及相关代码,恢复原生更新能力:
struct NodeList: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @FetchRequest( sortDescriptors: [ NSSortDescriptor(key: "user.vip", ascending: false), NSSortDescriptor(key: "lastHeard", ascending: false) ], animation: .default ) var nodes: FetchedResults<RadioInfoEntity> @State private var path: NavigationPath = NavigationPath() var body: some View { NavigationStack (path: $path) { List(nodes, id: \.self) { node in NodeListItem( node: node, connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num ) } .onAppear { if bleManager.context == nil { bleManager.context = context } } } } }
3. 修复Sheet意外关闭问题
Sheet关闭是因为refreshID导致整个List重渲染,行视图被重建后showingSheetView状态重置。移除refreshID后,List仅更新变化的行,Sheet状态不受影响。
4. 优化Core Data更新逻辑
增加空值判断避免崩溃,确保关联对象更新时上下文正确:
func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) { let fetchNodeInfoAppRequest: NSFetchRequest<RadioInfoEntity> = RadioInfoEntity.fetchRequest() fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) do { guard let radio = try context.fetch(fetchNodeInfoAppRequest).first else { print("⚠️ No RadioInfoEntity found for num: \(Int64(packet.from))") return } radio.id = Int64(packet.from) radio.num = Int64(packet.from) radio.lastHeard = Date() if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload), nodeInfoMessage.hasUser { guard let user = radio.user else { print("⚠️ RadioInfoEntity has no associated User") return } user.userId = nodeInfoMessage.user.id user.num = Int64(nodeInfoMessage.num) user.name = nodeInfoMessage.user.name } try context.save() print("💾 Updated NodeInfo from Node Info App Packet For: \(radio.num)") } catch let nsError as NSError { context.rollback() print("💥 Error Saving RadioInfoEntity from NODEINFO_APP \(nsError)") } }
5. 确保Core Data实体配置正确
- 在Xcode模型编辑器中,为
RadioInfoEntity和User的所有属性勾选Dynamic,确保属性变化能触发objectWillChange。 - 配置双向关联关系:
User实体添加反向关联指向RadioInfoEntity,确保更新User时,RadioInfoEntity能收到变化通知。
内容的提问来源于stack exchange,提问作者ron
相关产品推荐
相关产品推荐

