How to Save a Custom Class as an Attribute of a Coredata Entity in Swift 3

How do you save a custom class as an attribute of a CoreData entity in Swift 3?

The issue is that WorkoutRoutine is itself a custom class and as of your error it is not NSCoding compliant, therefore aCoder.encode(routine, forKey: "routine") doesn't really know how to encode it, as well as routine = aDecoder.decodeObject(forKey: "routine") as! [WorkoutRoutine] doesn't know how to decode it.

Not really related, but please try a safer approach for your coder and encoder initializer as the force unwrap might cause crashes if the encoder does not contain the keys you are looking for (for any reason)

required init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String,
let imageName = aDecoder.decodeObject(forKey: "imageName") as? String,
let routine = aDecoder.decodeObject(forKey: "routine") as? [WorkoutRoutine],
let shortDescription = aDecoder.decodeObject(forKey: "shortDescription") as? String else {
return nil
}
self.name = name
self.imageName = imageName
self.routine = routine
self.shortDescription = shortDescription
}

Saving custom class into Coredata

It's not enough to make the attribute transformable, you also need to arrange for the transformation to happen. You can't just tell Core Data to transform any old object and expect it to know what to do. You have a couple of options:

  1. Don't tell Core Data how to transform the data. In this case, Core Data will attempt to call encodeWithCoder: on your object to convert it to NSData. That's why you get the error that mentions this method-- it's trying to call the method on your class, but that method doesn't exist. In this case your class must conform to NSCoding for the transformation to occur.

  2. Tell Core Data how to transform the data. In this case you create a subclass of NSValueTransformer that performs the transformation. You configure this on the attribute, either in the Core Data model editor or in code. In this case, you must have a custom transformer class that knows how to perform the transformation.

How to save existing objects to Core Data

In your xcdatamodeld define an entity, such as User:

Sample Image

Add an attribute with a Transformable type. Name it 'books'.

Sample Image

Next, set the Transformable attribute's class to an array of Book. In the Custom Class field below.

Sample Image

Use the following code to get the current array from your current context. These methods should go in some sort of DataManager class (which should be a singleton):

import CoreData

open class DataManager: NSObject {

public static let sharedInstance = DataManager()

private override init() {}

// Helper func for getting the current context.
private func getContext() -> NSManagedObjectContext? {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return nil }
return appDelegate.persistentContainer.viewContext
}

func retrieveUser() -> NSManagedObject? {
guard let managedContext = getContext() else { return nil }
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "User")

do {
let result = try managedContext.fetch(fetchRequest) as! [NSManagedObject]
if result.count > 0 {
// Assuming there will only ever be one User in the app.
return result[0]
} else {
return nil
}
} catch let error as NSError {
print("Retrieving user failed. \(error): \(error.userInfo)")
return nil
}
}

func saveBook(_ book: Book) {
print(NSStringFromClass(type(of: book)))
guard let managedContext = getContext() else { return }
guard let user = retrieveUser() else { return }

var books: [Book] = []
if let pastBooks = user.value(forKey: "books") as? [Book] {
books += pastBooks
}
books.append(book)
user.setValue(books, forKey: "books")

do {
print("Saving session...")
try managedContext.save()
} catch let error as NSError {
print("Failed to save session data! \(error): \(error.userInfo)")
}
}

}

You'll also need a method to create a user (and likely delete, assuming we want to follow CRUD). First, you'll need to grab a reference to the User Entity to create one. This definition should be at the top of your DataManager class.

extension DataManager {
private lazy var userEntity: NSEntityDescription = {
let managedContext = getContext()
return NSEntityDescription.entity(forEntityName: "User", in: managedContext!)!
}()
}

And then implement this function to create one.

extension DataManager {
/// Creates a new user with fresh starting data.
func createUser() {
guard let managedContext = getContext() else { return }
let user = NSManagedObject(entity: userEntity, insertInto: managedContext)

do {
try managedContext.save()
} catch let error as NSError {
print("Failed to save new user! \(error): \(error.userInfo)")
}
}
}

Now just call:

DataManager.sharedInstance.createUser()

to create a new user.
Then, to append books to the user's storage:

DataManager.sharedInstance.saveBook(book)



Related Topics



Leave a reply



Submit