swift 从视图外部检查核心数据实体是否为空

mccptt67  于 2023-02-28  发布在  Swift
关注(0)|答案(1)|浏览(149)

我目前有一个ContentView,其中包含一个FetchRequest,我将其存储为变量。如果FetchRequest中的Core Data实体为空,我希望禁用一个菜单项Button(在macOS上)。以下是我的ContentView中的相关代码:

struct ContentView: View {

@Environment(\.managedObjectContext) private var viewContext
@SectionedFetchRequest(
    sectionIdentifier: \.startDateRelative,
    sortDescriptors: [NSSortDescriptor(keyPath: \MyEntity.startDate, ascending: false)],
    animation: .default)
    var items: SectionedFetchResults<String, MyEntity>
}

在"MyApp"中,我有:

@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
        .commands {
            CommandMenu("Database") {
                Button("Delete All") {
                    stuffToDo()
                }
                .disabled() // This is the problem, what do I put in the ()?
            }
        }
    }
}

问题是,如果我尝试添加.disabled(contentView.items.isEmpty),那么我会得到错误Accessing StateObject's object without being installed on a View. This will create a new instance each time.,它不起作用。
我曾经成功地检查过实体是否为空,但我需要在实体更改时更新,否则它将无法正常工作。
有什么想法吗?
编辑:持久性控制器:

import CoreData

struct PersistenceController {
    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

    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
    }
}
7cwmlq89

7cwmlq891#

你没有CoreData的问题,你有一个应用程序间的通信问题。你需要改变你的应用程序结构中的状态来启用和禁用CommandMenu。在最简单的世界里,你可以在你的应用程序文件中创建一个State变量,并将其绑定到你的ContentView中的@Binding,然后根据你的获取是否返回任何东西来根据需要简单地改变绑定。但是,我怀疑你有两个视图,因此,另一个稍微复杂一点的方法是允许通过环境更改该值,然后您可以从任何地方触发该更改(具有标准限制),并让其随意禁用或启用CommandMenu。
我首先创建了一个ObservableObject,如果您只需要在视图之间传递一个简单的绑定,并使用它来禁用或启用CommandMenu,那么这个对象就显得多余了:

import SwiftUI

class CommandMenuDisabled: ObservableObject {
    
    @Published var value = false
    
    func toggle() {
        value.toggle()
    }
}

// This creates the EnvironmentKey that you will inject into the environment.
struct CommandMenuDisabledKey: EnvironmentKey {
  static var defaultValue = CommandMenuDisabled()
}

// This tells the environment that the EnvironmentKey exists and how to handle it.
extension EnvironmentValues {
  var commandMenuDisabled: CommandMenuDisabled {
    get { self[CommandMenuDisabledKey.self] }
    set { self[CommandMenuDisabledKey.self] = newValue }
  }
}

接下来,我将其添加到App文件中:

@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared
    @StateObject var disabled = CommandMenuDisabled()

    
    var body: some Scene {
        WindowGroup {
            ContentView(commandMenuDisabled: $disabled.value) // If you are using an @Binding, send it here.
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
                .environment(\.commandMenuDisabled, disabled)

        }
        .commands {
            CommandMenu("Database") {
                Button("Delete All") {
                    stuffToDo()
                }
                .disabled(disabled.value) // Use the StateObject here
            }
        }
    }
}

还有一个简单的演示ContentView:

struct ContentView: View {
    @Environment(\.commandMenuDisabled) var disabled // Receive it here
    
    @Binding var commandMenuDisabled: Bool // or receive it here for @Binding
    
    var body: some View {
        VStack {
            // This button uses the @Environment value
            Button {
                disabled.toggle() // calls the toggle func in the ObservableObject
            } label: {
                Text("@Environment Toggle Command Menu")
            }

            // This button uses the @Binding value
            Button {
                commandMenuDisabled.toggle() // toggles the @Binding value
            } label: {
                Text("@Binding Toggle Command Menu")
            }
        }
        .padding()
    }
}

通过在环境中传递ObservableObject,您可以控制它,但是更新不会直接影响任何其他视图,除了App结构体。
编辑:
使用. onChange()可以在视图中观察CoreData实体:

.onChange(of: items.count) { newValue in
    commandMenuDisabled.toggle()
}

您可能需要在开始时禁用CommandMenu,然后在count〉0时启用它。

相关问题