SwiftUI Preview with Core Data: invalid NSEntityDescription - core-data

I'm trying to have my SwiftUI Previews work with an in memory Core Data Stack (from Xcode Template). As soon as I call Entity.entity(), I get the following error message:
let context = PersistenceController.preview.container.viewContext
let newBoatMO = Entity(entity: Entity.entity(), insertInto: context)
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An NSManagedObject of class 'Entity' must have a valid NSEntityDescription.'
I checked in that the name in NSPersistentCloudKitContainer(name: is correct, I also checked in my .xcdatamodeld, the Entity name is correct, the module is empty (ie Global Namespace), and I have this #objc(Entity) at the top of my NSManagedObject subclass.
If I use the non-memory Stack, the Preview works. It's as if the Model was not loaded if I use the in-memory Stack.

For me this was fixed by using public convenience init(context moc: NSManagedObjectContext) rather then the designated public init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?).
So:
Entity(context: context)
instead of
Entity(entity: Entity.entity(), insertInto: context)
Some background:
I had this error when I had 'overridden' the default convenience init(context moc: NSManagedObjectContext). So I solved it by switching (back) to the built-in version.
Before my Location+CoreDataClass looked like this:
#objc(Location)
public class Location: NSManagedObject {
convenience init(context moc: NSManagedObjectContext) {
self.init(entity: Location.entity(), insertInto: moc) // <- Error
timestamp = Date()
}
}
and afterwards like this:
#objc(Location)
public class Location: NSManagedObject {
convenience init(into c: NSManagedObjectContext, timestamp: Date = Date()) {
self.init(context: c) // <- No error
self.timestamp = timestamp
}
}
I'm not sure what causes the error, but maybe this helps someone into the right direction.

Related

CoreData bug in Xcode 12.1

I am working through Paul Hudson's 100 Days of SwiftUI and on Project 11 have hit a frustrating issue with CoreData. This is a direct lift of Paul's code that compiles and runs fine in his video. The Bookworm.xcdatamodeld has a single entity named Student that has two attributes: a UUID named id and a String named name.
It compiles fine, but running it results in a crash on the ForEach, with 'students' underlined in red. The error message that pops up in the console says:
2020-10-31 12:13:47.934507-0400 Bookworm[614:7766183] [error] error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'Bookworm.Student' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
CoreData: error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'Bookworm.Student' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
2020-10-31 12:13:47.934651-0400 Bookworm[614:7766183] [error] error: +[Bookworm.Student entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
CoreData: error: +[Bookworm.Student entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
2020-10-31 12:13:47.953419-0400 Bookworm[614:7766183] [SwiftUI] Context in environment is not connected to a persistent store coordinator: <NSManagedObjectContext: 0x6000008d0820>
I have searched a ton, and tried every recommended solution that I have found including: simply closing and reopening Xcode (Step 1), cleaning the project and then repeating Step 1, and deleting all the derived data and repeating Step 1. I have verified that Current Product Module is selected in the inspector for the Module, and that Codegen has Class Definition selected.
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Student.entity(), sortDescriptors: []) var students: FetchedResults<Student>
var body: some View {
VStack {
List {
ForEach(students, id: \.id) { student in
Text(student.name ?? "Unknown")
}
}
}
}
}
If you are using SwiftUI lifecycle, you should initialize NSPersistentContainer in a parent View (or App) and import managedObjectContext to the environment.
In your case, it could be something like this:
import SwiftUI
import CoreData
#main
struct coreDataParadigmApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
struct ContentView: View {
#FetchRequest(entity: Student.entity(), sortDescriptors: []) var students: FetchedResults<Student>
var body: some View {
VStack {
List {
ForEach(students, id: \.id) { student in
Text(student.name ?? "Unknown")
}
}
}
}
}
// DONT FORGET TO CHANGE THE NAME OF YOUR FILE
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init() {
container = NSPersistentContainer(name: "coreDataNameOfFile")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}

How to reload a row of SwiftUI Core Data-backed list if object properties change?

I have a standard SwiftUI list setup, powered by Core Data FetchRequest.
struct SomeView: View {
var container: Container
var myObjects: FetchRequest<MyObject>
init(container: Container) {
let predicate : NSPredicate = NSPredicate(format: "container = %#", container)
self.container = container
self.myObjects = FetchRequest<MyObject>(entity: MyObject.entity(), sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)], predicate: predicate)
}
var body: some View {
VStack(spacing: 0.0) {
List(myObjects.wrappedValue, id: \.uniqueIdentifier) { myObject in
rowView(for: myObject, from: self.myObjects.wrappedValue)
}
}
}
}
Everything works well when items are added and deleted. RowView returns a view that presents different content based on various properties of myObject.
Problem: when I modify a particular myObject elsewhere in the app (change one of its properties), and save the associated Core Data ManagedObjectContext, the List row representing that item is not updated/refreshed in the UI.
Possibly a cause for this is that I am updating my Core Data object by setting a property, that in turn sets another property. Maybe the associated signaling doesn’t reach the right place, and I should emit more notifications here.
Code in MyObject. ObjectType is an enum, typeValue is int32 backing this, that actually gets stored in CD database.
var type: ObjectType {
get {
return ObjectType(rawValue: typeValue)!
}
set {
self.typeValue = newValue.rawValue
}
}
How do I cause a list row to update when the backing Core Data object is modified and saved elsewhere in the app?
I finally figured this out on my own. The fix was not in the list, but further down the stack, in RowView.
RowView code was such:
struct RowView: View {
var myObject: MyObject
// Other code to render body etc
}
When doing this, the RowView works as expected, but it treats myObject as immutable. Any changes to myObject don’t cause a view redraw.
The one-keyword fix is to add #ObservedObject to the declaration:
struct RowView: View {
#ObservedObject var myObject: MyObject
}
It now works as expected, and any updates to MyObject cause a redraw.

Input a dynamic value into #FetchRequest, to fetch a single entity from core data in SwiftUI

I saw same type of error but with different kind of code here, so I think it's better to ask a new question on this context. I have attempted to "find a specific entity" from core data by trying to pass a string variable (which use as a key to find that entity) called title into #FetchRequest. This is part of the code I have used
struct AccountMainPage: View {
//*** User input ***
var title: String
//*** Core data enviroment initialisation ***
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: Accounts.getSpecificAccounts(findTitle: title)) var fetchedAccount: FetchedResults<Accounts>
var body: some View {
//SOME CODE HERE
}
}
The public class Accounts has the extension:
extension Accounts {
static func getSpecificAccounts(findTitle: String) -> NSFetchRequest<Accounts> {
let request: NSFetchRequest<Accounts> = Accounts.fetchRequest() as! NSFetchRequest<Accounts>
let findDescriptor = NSPredicate(format: "title == %#",findTitle)
request.predicate = findDescriptor
return request
}
}
However, the line with #FetchRequest(fetchRequest: Accounts.getSpecificAccounts(findTitle: title)) var fetchedAccount: FetchedResults<Accounts> has a syntax error:
Cannot use instance member 'title' within property initializer; property initializers run before 'self' is available
Is there something wrong with my code?
#FetchRequest is dynamic property which is initialised, as any other property, before your AccountMainPage init is called, so self is not available yet, that is why you cannot use title property which is a member of self, and that is about what compiler error tells.
So here is a possible solution: we initialise fetch request property with stub request and then in init, which is called later, reinitialise it with real fetch request.
Here is an approach demo (all unrelated things cut):
struct ContentView: View {
var title: String
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: Accounts.fetchRequest()) var fetchedAccount: FetchedResults<Accounts>
init(title: String) {
self.title = title
_fetchedAccount = FetchRequest<Accounts>(fetchRequest: Accounts.getSpecificAccounts(findTitle: title))
}
...

Core Data Framework With Multiple Targets

I am currently making a project in swift 4 which uses a few targets. Since all the targets need to access the same core data, I have decided to make my own framework target which stores the data model for it as well as the access information for it.
The problem I am having is in my application target (CoreDataTest), when I run the application, I get the following error:
2017-09-17 12:02:20.787132+0100 CoreDataTest[22070:3218298] [error] error: Failed to load model named TestData
CoreData: error: Failed to load model named TestData
2017-09-17 12:02:20.787594+0100 CoreDataTest[22070:3218298] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An NSManagedObject of class 'Message' must have a valid NSEntityDescription.'
*** First throw call stack:
(0x182097d38 0x1815ac528 0x18481a73c 0x1026c6678 0x1026c6708 0x1848bac5c 0x10261d480 0x10261ce94 0x10261cd48 0x10261cecc 0x18b42b96c 0x18b42b544 0x18b43210c 0x18b42f378 0x18b49edb4 0x18b68e570 0x18b693300 0x18b9129b4 0x18bbd90d0 0x18b912618 0x18b912e88 0x18c05daf4 0x18c05d998 0x18bde7ab4 0x18bf77c94 0x18bde7964 0x18bbd8730 0x18b691a44 0x18ba80144 0x18472d968 0x184736270 0x10344145c 0x10344db74 0x184761b04 0x1847617a8 0x184761d44 0x182040358 0x1820402d8 0x18203fb60 0x18203d738 0x181f5e2d8 0x183de3f84 0x18b48f5e0 0x10261deec 0x181a8256c)
libc++abi.dylib: terminating with uncaught exception of type NSException
I added an exception break point and it crashes in ViewController.swift at PersistenceStorage.saveContext().
How would I create a framework to create a shared Core Data database throughout my multiple targets in a single project?
Here is my project setup. Please note that each group is its own target.
Target: CoreDataKit (Framework)
CoreData.swift
import CoreData
public class PersistenceStorage {
private init() {}
public static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
public static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "TestData")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
public static func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Message+CoreDataClass.swift
import Foundation
import CoreData
#objc(Message)
public class Message: NSManagedObject {
}
Message+CoreDataProperties.swift
import Foundation
import CoreData
extension Message {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Message> {
return NSFetchRequest<Message>(entityName: "Message")
}
#NSManaged public var text: String?
}
Target: CoreDataTest (Main Application)
ViewController.swift
import UIKit
import CoreDataKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let message = Message(context: PersistenceStorage.context)
message.text = "test"
PersistenceStorage.saveContext()
}
}
AppDelegate.swift
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
}
After a lot of research and trial and error, I found that I need to set the target members on the xcdatamodel file to the other targets that I wanted it to be shared with.
Just bumped into this myself so thought I'd post a couple other options.
Since you aren't subclassing NSPersistentContainer then it doesn't know where to look for bundles so has no option but to look in the main bundle.
If you want to only have your model in your framework then you could subclass NSPersistentContainer, or you can load the model first and pass that to the initialization of the persistent container. If you have only a single model then doing something like mergedModelFromBundles is pretty simple to do and then pass that model to the persistent container initializer.

Resolving 'Failed to call designated initializer on NSManagedObject class'

I'm new to Swift and I'm trying to learn how to use Core Data. But I'm getting this error and I'm not sure what I've done wrong. I've searched online and tried a few things but I can't get it right.
Failed to call designated initializer on NSManagedObject class 'FirstCoreData.Course'
When this line executes:
ncvc.currentCourse = newCourse
In this function:
class TableViewController: UITableViewController, AddCourseViewControllerDelegate {
var managedObjectContext = NSManagedObjectContext.init(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addCourse" {
let ncvc = segue.destinationViewController as! NewCourseViewController
ncvc.delegate = self
let newCourse = NSEntityDescription.insertNewObjectForEntityForName("Course", inManagedObjectContext: self.managedObjectContext) as! Course
ncvc.currentCourse = newCourse
}
}
Class generated by "Create NSManagedObject Subclass..." for Course entity:
import Foundation
import CoreData
class Course: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
And:
import Foundation
import CoreData
extension Course {
#NSManaged var title: String?
#NSManaged var author: String?
#NSManaged var releaseDate: NSDate?
}
The problem lies not in the code in your question, but in the snippet you included as comments to the other answer:
var currentCourse = Course()
This doesn't just declare currentCourse to be of type Course, it also creates an instance of the Course entity using the standard init method. This is expressly not allowed: You must use the designated initialiser: init(entity entity: NSEntityDescription,
insertIntoManagedObjectContext context: NSManagedObjectContext?). This is described in the Apple Documentation here.
I suspect you do not ever use the instance created by the above var definition, so just define it as being of type Course?:
var currentCourse : Course?
Since it is optional, you do not need to set an initial value, though you will need to unwrap the value whenever it is used.
The simplest way is this:
Define in the applicationDelegate a reference for the context
Instantiate the variable by passing the context
In the AppDelegate (outside the brackets):
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
And in the code:
let currentCourse = Course(context:context)
Now you have your entity created. But don't forget to save with:
appDelegate.saveContext()
I had the same issue. And instantiating the object like this worked, for your course it would be something like this:
var currentCourse = Course.init(entity: NSEntityDescription.entityForName("Course", inManagedObjectContext:mox)!, insertIntoManagedObjectContext: mox)
instead of:
var currentCourse = Course()
I used this in Xcode 8.3.2 with Swift 3.1.
NSEntityDescription.insertNewObject(forEntityName: String(describing: type(of: Record())), into: managedObjectContext) as! Record
And got the same error message. But this data was inserted into db. So maybe this doesn't matter.
Your currentCourse should be NSManagedObject class
Please refer this CoreData: error: Failed to call designated initializer on NSManagedObject class

Resources