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
Related
In a parent view, I have this:
LongPressEditableText(contents: "\(workout.name ?? "")", context: workout, keyPath: \WorkoutEntity.name)
referencing a string field of a WorkoutEntity in CoreData.
The LongPressEditableText is to be a component which is usually just a Text(), but when long pressed, becomes a TextField with the same contents, editable. On submit it should update the UI (it does this fine), but it should also save the new value to the appropriate spot in CoreData.
struct LongPressEditableText: View {
#State var contents: String
#Environment(\.managedObjectContext) private var viewContext
var context: NSObject
var keyPath: KeyPath<NSObject, String?>
#State var inEditMode: Bool = false
var body: some View {
if inEditMode {
TextField("test", text: $contents)
.onSubmit {
context[keyPath: keyPath] = contents
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
inEditMode.toggle()
}
} else {
Text(contents)
.onLongPressGesture {
inEditMode.toggle()
}
}
}
}
At the moment, I get two errors. In my parent view Cannot convert value of type 'KeyPath<WorkoutEntity, String?>' to expected argument type 'KeyPath<NSObject, String?>' and in the LongPressEditableText view Cannot assign through subscript: key path is read-only
I can solve the first by forcing KeyPath but that's not a solution as I want the editable field to work with a number of different entities with string fields, so I'd like it to be generic. The second I am stumped about, this is as close as I've been able to get to success.
"Generics isn’t my primary concern...", yes it is because it is a very helpful solution here that tells the compiler and runtime what type of object is used in the text field.
First of all since this is Core Data we shouldn't use NSObject but instead NSManagedObject so lets make the view generic with a type that inherits from NSManagedObject and then use the generic type inside for the properties.
struct LongPressEditableText<ManagedObject: NSManagedObject>: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var contents: String = ""
#State var object: ManagedObject
var keyPath: ReferenceWritableKeyPath <ManagedObject, String?>
Notice that the property object (context in your code) is declared to be of the generic type and that the keyPath is also defined to hold the same type. I have also changed from KeyPath to ReferenceWritableKeyPath since the generic type is a class and we want to use the key path to update the object.
And to use the field here is an example, since the view is generic the compiler can deduct that the generic type is Item and also check that it has a property text
struct DetailView: View {
#ObservedObject var item: Item
var body: some View {
VStack {
LongPressEditableText(object: item, keyPath: \.text)
}
.padding()
}
}
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.
How can I achieve what the following code achieves in SwiftUI?
Class *object = [[Class alloc] initWithNibName:#"nibname" bundle:nil];
Creating a constant with a type class is obviously not working without initialization:
let objectName : myClass
If you set up Objective-C/Swift bridge correctly then just add
#import "myClass.h" // your class header in YourProject-Bridging-Header.h
and use in .swift as in following example
struct ContentView: View {
let objectName = myClass(nibName: "nibname", bundle: nil)
// ... other code
}
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))
}
...
In CoreData, I have defined an unordered to-many relationship from Node to Tag. I've created an Swift entity like this:
import CoreData
class Node : NSManagedObject {
#NSManaged var tags : Array<Tag>
}
Now I want to add a Tag to an instance of Node, like this:
var node = NSEntityDescription.insertNewObjectForEntityForName("Node", inManagedObjectContext: managedObjectContext) as Node
node.tags.append(tag)
However, this fails with the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-many relationship: property = "tags"; desired type = NSSet; given type = _TtCSs22ContiguousArrayStorage000000000B3440D4; value = (
"<_TtC8MotorNav3Tag: 0xb3437b0> (entity: Tag; id: 0xb343800 ; data: {...})"
).'
What is the correct type for to-many relationships?
To be able to work with one-to-many relationship in Swift you need to define property as:
class Node: NSManagedObject {
#NSManaged var tags: NSSet
}
If you try to use NSMutableSet changes will not be saved in CoreData. And of course it is recommended to define reverse link in Node:
class Tag: NSManagedObject {
#NSManaged var node: Node
}
But still Swift cannot generate dynamic accessors in runtime, so we need to define them manually. It is very convenient to define them in class extension and put in Entity+CoreData.swift file. Bellow is content of Node+CoreData.swift file:
extension Node {
func addTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.addObject(value)
}
func removeTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.removeObject(value)
}
}
Usage:
// somewhere before created/fetched node and tag entities
node.addTagObject(tag)
Important: To make it all work you should verify that class names of entities in you CoreData model includes your module name. E.g. MyProjectName.Node
As of Xcode 7 and Swift 2.0, the release note 17583057 states:
The NSManaged attribute can be used with methods as well as
properties, for access to Core Data’s automatically generated
Key-Value-Coding-compliant to-many accessors.
#NSManaged var employees: NSSet
#NSManaged func addEmployeesObject(employee: Employee)
#NSManaged func removeEmployeesObject(employee: Employee)
#NSManaged func addEmployees(employees: NSSet)
#NSManaged func removeEmployees(employees: NSSet)
These can be declared in your NSManagedObject subclass. (17583057)
So you just have to declare the following methods and CoreData will take care of the rest:
#NSManaged func addTagsObject(tag: Tag)
#NSManaged func removeTagsObject(tag: Tag)
#NSManaged func addTags(tags: NSSet)
#NSManaged func removeTags(tags: NSSet)
Actually you can just define:
#NSManaged var employees: Set<Employee>
And use the insert and remove methods of the Set directly.
Building on #Keenle's answer, if you want to be cheeky and concise and be able to say
node.tags.append(tag)
one can wrap the call to self.mutableSetValueForKey:
class Node: NSManagedObject {
var tags: NSMutableOrderedSet {
return self.mutableOrderedSetValueForKey("tags")
}
}