Environment 属性 wrapper throws +entityForName: nil 不是用于搜索实体名称的合法 NSPersistentStoreCoordinator

Environment property wrapper throws +entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name

为什么 Environment(\.managedObjectContext).wrappedValue 上的 entityForName 总是 nil? 我收到此错误 +entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Project'

使用 @Environment(\.managedObjectContext) var viewContext 我没有收到此错误。 但是我需要用我的控制器初始化视图,需要传递 NSManagedObjectContext

有人可以帮助理解为什么这两行 return 不是同一个对象吗?还是一样?

@main
struct umbrellaApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}
struct ContentView: View {
    @Environment(\.managedObjectContext) var viewContext // Works
    @StateObject private var controller: ContentViewController
    
    init() {
        // Crashes
        let viewContextValue = Environment(\.managedObjectContext).wrappedValue

        let controller = ContentViewController(managedObjectContext: viewContextValue)
        self._controller = StateObject(wrappedValue: controller)
    }
    
    
    var body: some View {
        NavigationView {
            Text("Hello World")
        }
    }
}

ContentViewController 的初始值设定项。

init(managedObjectContext: NSManagedObjectContext) {
    self.managedObjectContext = managedObjectContext
    self.projectsController = NSFetchedResultsController(fetchRequest: Project.projectsFetchRequest,
      managedObjectContext: managedObjectContext,
      sectionNameKeyPath: nil, cacheName: nil)
        
    super.init()

    projectsController.delegate = self
    do {
        try projectsController.performFetch()
        self.projects = projectsController.fetchedObjects ?? []
    } catch {
        print("failed to fetch projects!")
    }
}

简短回答,Environment 需要 @,它是一个包装器。您尝试做的不是 Environment

的使用记录

https://developer.apple.com/documentation/swiftui/environment

长答案,

您没有提供最小可复制产品,但这是我看到的

    let viewContextValue = Environment(\.managedObjectContext).wrappedValue
     

它不起作用,因为如您所知 @Environment 此时不可用,或者您只需使用 viewContext.

    let controller = ContentViewController(managedObjectContext: viewContextValue)

我明白你在这里想做什么,但如上所述 @Environment 只是在 init

期间不可用
    self._controller = StateObject(wrappedValue: controller)

虽然它在表面上“有效”,但它有点破坏了 StateObject

的优点

SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view. Instead, SwiftUI provides the StateObject attribute for this purpose. You can safely create a Book instance inside a view this way: @StateObject var book = Book() https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

根据我的经验,SwiftUI 中的自定义 init 无法提供可靠的体验。我尽量远离他们。如果您必须在 init 上进行自定义工作,请在 class 中将其作为 ViewModel/ViewController,这也是 ObservableObjectView不应该做任何工作。

如果您想要替代您想要执行的操作,请参阅

你只需要

let persistenceController = PersistenceController.shared

在您的 ContentViewController 中并像这样初始化您的 StateObject

@StateObject private var controller: ContentViewController = ContentViewController()

这是一个示例,其中我使用了 FetchedResultsController 它有部分

import SwiftUI
import CoreData
class TaskListViewModel: ObservableObject {
    let persistenceController = PersistenceController.previewAware()
    @Published var fetchedResultsController: NSFetchedResultsController<Task>?
    
    init() {
        setupController()
    }
    func setupController() {
        do{
            fetchedResultsController = try retrieveFetchedController(sortDescriptors: nil, predicate: nil, sectionNameKeyPath: #keyPath(Task.isComplete))
        }catch{
            print(error)
        }
    }
    func deleteObject(object: Task) {
        persistenceController.container.viewContext.delete(object)
        save()
    }
    func save() {
        do {
            if persistenceController.container.viewContext.hasChanges{
                try persistenceController.container.viewContext.save()
                objectWillChange.send()
            }else{
            }
        } catch {
            print(error)
        }
    }
}
//MARK: FetchedResultsController setup
extension TaskListViewModel{
    func retrieveFetchedController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
        
        return try initFetchedResultsController(sortDescriptors: sortDescriptors, predicate: predicate, sectionNameKeyPath: sectionNameKeyPath)
    }
    private func initFetchedResultsController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
        fetchedResultsController = getFetchedResultsController(sortDescriptors: sortDescriptors, predicate: predicate, sectionNameKeyPath: sectionNameKeyPath)
        //fetchedResultsController!.delegate = self
        do {
            try fetchedResultsController!.performFetch()
            return fetchedResultsController!
            
        } catch {
            print( error)
            throw error
        }
    }
    func getFetchedResultsController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, sectionNameKeyPath: String) -> NSFetchedResultsController<Task> {
        
        return NSFetchedResultsController(fetchRequest: getEntityFetchRequest(sortDescriptors: sortDescriptors, predicate: predicate), managedObjectContext: persistenceController.container.viewContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: nil)
    }
    private func getEntityFetchRequest(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) -> NSFetchRequest<Task>
    {
        
        let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
        fetchRequest.includesPendingChanges = false
        fetchRequest.fetchBatchSize = 20
        if sortDescriptors != nil{
            fetchRequest.sortDescriptors = sortDescriptors
        }else{
            fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Task.dateAdded), ascending: false)]
        }
        if predicate != nil{
            fetchRequest.predicate = predicate
        }
        return fetchRequest
    }
}
struct TaskListView: View {
    @StateObject var vm: TaskListViewModel = TaskListViewModel()
    @State var taskToEdit: Task?
    var body: some View {
        
        if vm.fetchedResultsController?.sections != nil{
            List{
                ForEach(0..<vm.fetchedResultsController!.sections!.count){idx in
                    let section = vm.fetchedResultsController!.sections![idx]
                    TaskListSectionView(objects: section.objects as? [Task] ?? [], taskToEdit: $taskToEdit, sectionName: section.name).environmentObject(vm)
                    
                }
            }.sheet(item: $taskToEdit, onDismiss: {
                vm.save()
            }){editingTask in
                TaskEditView(task: editingTask)
            }
            
        }else{
            Image(systemName: "empty")
        }
    }
}

struct TaskEditView: View {
    @ObservedObject var task: Task
    var body: some View {
        TextField("name", text: $task.name.bound)
    }
}
struct TaskListSectionView: View {
    @EnvironmentObject var vm: TaskListViewModel
    let objects: [Task]
    @State var deleteAlert: Alert = Alert(title: Text("test"))
    @State var presentAlert: Bool = false
    @Binding var taskToEdit: Task?
    @State var isExpanded: Bool = true
    var sectionName: String
    
    var body: some View {
        Section(header: Text(sectionName) ,                content: {
            ForEach(objects, id: \.self){obj in
            let task = obj as Task
            Button(action: {
                taskToEdit = task
            }, label: {
                Text(task.name ?? "no name")
            })
            .listRowBackground(Color(UIColor.systemBackground))
            
            
        }.onDelete(perform: deleteItems)
        })
        
    }
    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            deleteAlert = Alert(title: Text("Sure you want to delete?"), primaryButton: Alert.Button.destructive(Text("yes"), action: {
                let objs = offsets.map { objects[[=15=]] }
                
                for obj in objs{
                    
                    vm.deleteObject(object: obj)
                }
                //Because the objects in the sections aren't being directly observed
                vm.objectWillChange.send()
                
            }), secondaryButton: Alert.Button.cancel())
            
            
            self.presentAlert = true
            
        }
    }
}

struct TaskListView_Previews: PreviewProvider {
    static var previews: some View {
            TaskListView()
        
    }
}

previewAware() 只是一种决定是否传递内置 previewshared

的方法
static func previewAware() -> PersistenceController{
    if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
        return PersistenceController.preview
    }else{
        return PersistenceController.shared
    }
}