【问题标题】:Environment property wrapper throws +entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity nameEnvironment property wrapper throws +entityForName: nil is not a legal NSPersistentStoreCoordinator for search entity name
【发布时间】:2021-08-04 01:24:46
【问题描述】:

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

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

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

@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!")
    }
}

【问题讨论】:

    标签: ios swift core-data swiftui property-wrapper


    【解决方案1】:

    简短的回答,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 可能随时创建或重新创建视图,因此这很重要 用给定的输入集初始化视图总是会导致 相同的看法。结果,创建观察对象是不安全的 在一个视图里面。相反,SwiftUI 为 这个目的。您可以在此视图中安全地创建 Book 实例 大大地: @StateObject var book = Book() https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

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

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

    你只需要

    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[$0] }
                    
                    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() 只是一个方法,它决定是通过内置的preview 还是shared

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

    【讨论】:

    • 我想将PreviewProvider 中的视图上下文传递给我的视图,以便在 Xcode 中预览。我看到你的PersistanceController 上有previewAware()。这能解决我的问题吗?
    • 这是我做的一个说服方法。 PersistenceController 自动提供preview 控制器,如果标准代码检测到您正在预览中运行,则该控制器会生成该控制器。上面的代码只需在PersistenceController中添加func
    • 这解决了我的问题。有了这个函数,我不需要在预览和代码中传递Environment 中的managedObjectContext。我只是在我的控制器中调用PersistanceController.previewAware() 并且有我的viewContext。谢谢!
    • 是的,您也可以在您的View 中使用previewAware()。这样你就完全不用担心改变它了。在我的PersistenceController 中,我有sharedprivate 这样我就不会意外调用它。
    猜你喜欢
    • 1970-01-01
    • 2021-10-28
    • 2022-06-30
    • 1970-01-01
    • 1970-01-01
    • 2016-05-24
    • 1970-01-01
    • 2021-10-08
    • 1970-01-01
    相关资源
    最近更新 更多