0

为什么 entityForNameEnvironment(\.managedObjectContext).wrappedValue总是为零?我收到这个错误+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!")
    }
}
4

1 回答 1

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 为此目的提供了 StateObject 属性。您可以通过这种方式在视图中安全地创建 Book 实例: @StateObject var book = Book() https ://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

根据我的经验init,SwiftUI 中的 custom 不能提供可靠的体验。我尽量远离他们。如果您必须init在 a classas a ViewModel/中进行自定义工作,ViewController那也是ObservableObjecta aView不应该做任何工作。

如果您想要替代您想要执行的操作,请参阅此 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()只是一种决定是否通过内置previewshared

static func previewAware() -> PersistenceController{
    if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
        return PersistenceController.preview
    }else{
        return PersistenceController.shared
    }
}
于 2021-05-13T20:15:56.563 回答