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:
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 toNSData
. 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 toNSCoding
for the transformation to occur.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
:
Add an attribute with a Transformable type. Name it 'books'.
Next, set the Transformable attribute's class to an array of Book. In the Custom Class
field below.
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
Swift. Declaring Private Functions in Internal Protocol
Swift: Search Bar Created at Auto Focus
Swift 5 Coredata Predicate Using Uuid
How to Replicate Hash_Hmac('Sha256', $Key, $Secret_Key) Function in Swift 4
Guard Let Error: Initializer for Conditional Binding Must Have Optional Type Not 'String'
Simple Observable Struct with Rxswift
How to Convert Unmanaged<Cfdata> to Nsdata
Swift Radio Streaming Avplayer
See Characters in an Nscharacterset (Swift)
How to Handle Menu Button Action in Tvos Remote
Implementing a Hash Combiner in Swift
Diffrence Between Function and Generic Function in Swift
How to Incorporate Swift Package Manager to an Existing Xcode Project
Swift Lazy Stored Property Versus Regular Stored Property When Using Closure
Use Quick Look Inside a Swift Cocoa Application to Preview Audio Files