如何在视图外部判断Core Data实体是否为空并禁用按钮
在macOS SwiftUI项目中,ContentView通过@SectionedFetchRequest获取MyEntity类型的Core Data数据,需要让MyApp的CommandMenu里的“Delete All”按钮在实体为空时自动禁用。直接访问ContentView的items.isEmpty会报错,且无法实现数据变化时的状态自动更新。
解决方案思路
核心是监听Core Data上下文的变化,实时获取实体数量,不能直接访问View实例的属性(因为View实例会被频繁重建)。以下两种方法均可实现需求:
方法一:修改PersistenceController为ObservableObject
将原有的PersistenceController结构体改为类并实现ObservableObject,添加发布属性跟踪实体是否存在,同时监听上下文变化自动更新状态。
修改后的PersistenceController代码
import CoreData import Combine class PersistenceController: ObservableObject { static let shared = PersistenceController() static var preview: PersistenceController = { let result = PersistenceController(inMemory: true) let viewContext = result.container.viewContext for _ in 0..<10 { let newItem = MyEntity(context: viewContext) newItem.startTime = Date() } do { try viewContext.save() } catch { let nsError = error as NSError print("Unresolved error during save \(nsError), \(nsError.userInfo)") } return result }() let container: NSPersistentCloudKitContainer @Published private(set) var hasEntities = false private var cancellables = Set<AnyCancellable>() init(inMemory: Bool = false) { container = NSPersistentCloudKitContainer(name: "MyApp") if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { print("Unresolved data error \(error), \(error.userInfo)") } }) container.viewContext.automaticallyMergesChangesFromParent = true // 监听上下文变化,自动更新实体状态 container.viewContext.publisher(for: \.hasChanges) .debounce(for: 0.1, scheduler: DispatchQueue.main) .sink { [weak self] _ in self?.updateHasEntities() } .store(in: &cancellables) // 初始化时检查一次实体状态 updateHasEntities() } private func updateHasEntities() { let request: NSFetchRequest<MyEntity> = MyEntity.fetchRequest() do { let count = try container.viewContext.count(for: request) hasEntities = count > 0 } catch { print("Failed to count entities: \(error)") hasEntities = false } } }
修改后的MyApp代码
@main struct MyApp: App { @StateObject private var persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) } .commands { CommandMenu("Database") { Button("Delete All") { // 批量删除逻辑 let fetchRequest: NSFetchRequest<NSFetchRequestResult> = MyEntity.fetchRequest() let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) do { try persistenceController.container.viewContext.execute(deleteRequest) try persistenceController.container.viewContext.save() } catch { print("Failed to delete all entities: \(error)") } } .disabled(!persistenceController.hasEntities) } } } }
方法二:单独创建数据监听类
如果不想修改原有的PersistenceController结构,可以单独创建一个DataMonitor类来监听实体状态。
DataMonitor代码
import CoreData import Combine class DataMonitor: ObservableObject { @Published var hasEntities = false private let context: NSManagedObjectContext private var cancellables = Set<AnyCancellable>() init(context: NSManagedObjectContext) { self.context = context // 监听上下文变化 context.publisher(for: \.hasChanges) .debounce(for: 0.1, scheduler: DispatchQueue.main) .sink { [weak self] _ in self?.updateEntityStatus() } .store(in: &cancellables) // 初始化检查 updateEntityStatus() } private func updateEntityStatus() { let request: NSFetchRequest<MyEntity> = MyEntity.fetchRequest() do { hasEntities = try context.count(for: request) > 0 } catch { hasEntities = false print("Failed to check entity count: \(error)") } } }
修改后的MyApp代码
@main struct MyApp: App { let persistenceController = PersistenceController.shared @StateObject private var dataMonitor: DataMonitor init() { self.dataMonitor = DataMonitor(context: persistenceController.container.viewContext) } var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) } .commands { CommandMenu("Database") { Button("Delete All") { // 批量删除逻辑 let fetchRequest: NSFetchRequest<NSFetchRequestResult> = MyEntity.fetchRequest() let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) do { try persistenceController.container.viewContext.execute(deleteRequest) try persistenceController.container.viewContext.save() } catch { print("Delete failed: \(error)") } } .disabled(!dataMonitor.hasEntities) } } } }
补充说明
- 使用
debounce是为了避免上下文频繁变化时触发过多的计数查询,提升性能。 - 两种方法都能实现数据变化时自动更新按钮的禁用状态,选择哪种取决于项目结构偏好。
内容的提问来源于stack exchange,提问作者Ricky Kresslein
相关产品推荐
相关产品推荐

