8
votes

I'm trying to run a basic test SwiftUI app for macOS using Core Data, and I'm hitting a problem. When I use this in my view:

@FetchRequest(entity: Note.entity(), sortDescriptors: []) let notes: FetchedResults<Note>

The app crashes with the following errors:

NoteTaker [error] error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'NoteTaker.Note' so +entity is confused.  Have you loaded your NSManagedObjectModel yet ?
CoreData: error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'NoteTaker.Note' so +entity is confused.  Have you loaded your NSManagedObjectModel yet ?

NoteTaker [error] error: +[NoteTaker.Note entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
CoreData: error: +[NoteTaker.Note entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass

NoteTaker executeFetchRequest:error: A fetch request must have an entity.

Now, if I used the alternate form of FetchRequest, it then works fine though the code is much uglier:

// outside of the view
func getAllNotes() -> NSFetchRequest<Note> {
  let request: NSFetchRequest<Note> = Note.fetchRequest()
  request.sortDescriptors = []
  return request
}

// in the view
@FetchRequest(fetchRequest: getAllNotes()) var notes: FetchedResults<Note>

Also, if I turn this into an iOS app instead, then the entity version of the @FetchRequest works fine.

Any ideas?

3
Whilst writing this, I found that if I change the auto-generated persistantContainer code in AppDelegate so it's no longer lazy then it works. So it looks like it's an issue with the container not being instantiated before the entity style of fetchRequest is made. Whilst this is a fix, it's a bit janky as obviously we only want to handle the cost of instantiating the persistant container when we really need to, and not when the app loads (hence the point of it being lazy). Also it feels really bad that it's going against when Apple is auto-creating for you. - Deano
Did you ever get this resolved? - Daniel
What was the fix? - Oleg991
Removing "lazy" below "// MARK: - Core Data stack" in the auto-generated AppDelegate.swift also worked for me. Wasted tons of hours with this. - Florian Schulz
If you think about it, this makes perfect sense. The app delegate creates ContentView() before then modifying it with environment(\.managedObjectContext, persistentContainer.viewContext). So the persistent container is lazily created after ContentView is created with its @FetchRequest property. If you were to have a view created inside of the body that had an@FetchRequest property instead, that would work as well since body is called later. - Jason Bobier

3 Answers

1
votes

I was getting this error in several places in my code for fetching and dynamic fetching requests as well as when creating core data objects this is what worked for me:

During fetch requests I used this:

@FetchRequest(entity: NSEntityDescription.entity(forEntityName: "Your Entity Name", in: managedObjectContext), sortDescriptors: [NSSortDescriptor(key: "Your Key", ascending: true)])

In dynamic fetch requests I used this:

var entity: Entity
var fetchRequest: FetchRequest<Your Entity>
init(_ entity: Entity) {
self.entity = entity
self.fetchRequest = FetchRequest(entity: NSEntityDescription.entity(forEntityName: "Your Entity", in: managedObjectContext), sortDescriptors: [NSSortDescriptor(key: "Your Key", ascending: true)], predicate: NSPredicate(format: "entity.uniqueIdentifier == %@", entity.uniqueIdentifier!))
}

var youEntities: FetchedResults<Your Entity> {
fetchRequest.wrappedValue
}

When creating core data objects:

let entityDescription = NSEntityDescription.entity(forEntityName: "Your Entity", in: managedObjectContext)

let object = Your Entity(entity: entityDescription!, insertInto: managedObjectContext)
0
votes

Use the Storyboard template and then set up the view using SwiftUI in the view controller.

override func viewWillAppear() {
        super.viewWillAppear()

        let context = (NSApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        let contentView = ContentView().environment(\.managedObjectContext, context)

        view = NSHostingView(rootView: contentView)
    }
-1
votes

Adding this line: .environment(.managedObjectContext, context)

in the SceneDelegate.swift

at

// Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
// Add `@Environment(\.managedObjectContext)` in the views that will need the context.
let contentView = ContentView().environment(\.managedObjectContext, context)