Store Data in Custom Class Array in Core Data

Saving an array of custom classes in Core Data

I was missing a few steps. In addition to the code in the question, the following had to be done to get this to work:

• Change the attribute's Custom Class in the model editor to NSArray

• Create a new class called MyCustomClassTransformer:

@interface MyCustomClassTransformer: NSSecureUnarchiveFromDataTransformer {}
@end

@implementation MyCustomClassTransformer
+ (Class)transformedValueClass {
return [MyCustomClassTransformer class];
}
+ (BOOL)allowsReverseTransformation {
return YES;
}

+ (NSArray<Class> *)allowedTopLevelClasses {
return @[[MyCustomClass class], [NSArray class]];
}
@end

• Register new transformer in AppDelegate's "didFinishLaunchingWithOptions":

MyCustomClassTransformer *transformer = [[MyCustomClassTransformer new];
[NSValueTransformer setValueTransformer:transformer forName: @"MyCustomClassTransformer"];

Storing Custom Class type array in Coredata

It sounds like you want a recursive data structure, where each node can have many other nodes as children and another node as its parent. This is what CoreData relationships are for.

Under your Node entity:

  1. Add a relationship children of type "To Many" with destination Node. No inverse.
  2. Add a relationship parent of Type "To One" with destination Node, set its inverse to children
  3. Go back to the children relationship and set its inverse to parent
  4. Consider what the delete rules should be. I suggest "Cascade" for children, meaning if you delete a parent all of its children are deleted too. "Nullify" makes sense for parent, meaning if you delete a child it just removes the parent's connection to that one child.

How to Save an Array of Custom Objects All At Once to a Core Data Store in Swift

I finally figured out my error. I have not used relationships before, so it didn't dawn on me what I was doing. When I was accessing MassOfPartEntity through its relationship with Widget, I was sending a Set, my custom object through. However, I had to send a Set, the actual entity through. Therefore, I changed saveWidget to this:

func saveWidget(){
print("saveWidget() entered")
if self.massOfSubpartMeasurementDict.count > 0,
self.massOfSubpartMeasurementDict.count == self.numberOfSubparts,
self.manufacturer != "",
self.title != "" {

let massOfPartEntity = MassOfPartEntity(context: self.managedObjectContext)
var massPerSubpartEntityArray: [MassOfPartEntity]
massPerSubpartEntityArray = massOfSubpartMeasurementDict.map {
massOfPartEntity.subPart = Int16($0.key)
if let massDouble = Double($0.value) {
massOfPartEntity.mass = Measurement(value: massDouble, unit: massUnit)
} else {
massOfPartEntity.mass = Measurement(value: 0.0, unit: massUnit)
}
return massOfPartEntity
}
let massOfSubpartSet = Set(massPerSubpartEntityArray) as NSSet

let widget = Widget(context: self.managedObjectContext)
widget.id = UUID()
widget.madeBy?.name = self.manufacturer
widget.addToHasMassOfPart(massOfSubpartSet) // FIXME: The crash is here
widget.title = self.title

do {
print("SaveWidget() attempted/n")
try self.managedObjectContext.save()
} catch {
print("SaveWidget() failed/n")
// handle the Core Data error
// Show alert as to status
}
}
}

changing the Map function return so that it returned MassOfPartEntity's that eventually became a Set. Essentially, it was a type mismatch.

Thanks to those who responded as talking it out definitely helped resolve this.

Swift: Store arrays of custom classes in Core Data

You are correct that having custom arrays in Core Data consists of creating Core Data objects for those items and connecting them via relationships just as you have done in the graph you posted.

How do I continue (simply click 'CreateNSManagedObject Subclass...'?)?

Now that you have an object graph, the next step largely depends on whether you have Xcode 7 or Xcode 8. In the case of the former, you should click that create subclass button. Then, if anything changes in your data model, you'll need to regenerate the subclasses again.

In the latter (Xcode 8), however, all you need to do is look at the "Codegen" dropdown in the attributes inspector when an entity is selected in the Core Data object model file. If you select "Class Definition", Xcode 8 should generate the class for you. "Category/Extension" means it will create an extension with all of the code necessary for Core Data access, and you need to declare the actual class definition. In either case (in Xcode 8) these are updated automatically when you change the object model (currently only after rebuilding, and they won't be visible).

Image from Core Data WWDC16 session

Xcode 8

How do I read it?

Assuming you haven't set up ordering in Core Data, it will come back as an NSSet, but you may convert it to an array:

reptileInstance.lengths.allObjects as! [Length]

How do I add a instance to the array?

You can either do something simple like:

lengthInstance.reptile = reptileInstance

In which case lengthInstance will automatically be added to the lengths collection property of reptileInstance, or you can set a new NSSet to lengths on reptileInstance.

This is a very simplified explanation. You should check out the Core Data Programming Guide, but note it may or may not be yet updated for the upcoming Xcode 8.

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
}


Related Topics



Leave a reply



Submit