You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在视图外部判断Core Data实体是否为空并禁用按钮

在macOS SwiftUI中动态禁用基于Core Data状态的CommandMenu按钮

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 04:15:51