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:
- Add a relationship
children
of type "To Many" with destinationNode
. No inverse. - Add a relationship
parent
of Type "To One" with destinationNode
, set its inverse tochildren
- Go back to the
children
relationship and set its inverse toparent
- 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 forparent
, 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
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
Self. in Trailing Swift Closures, Meaning and Purpose
Render Images on iOS 14 Widgets
Collection View Layout Bug When Selectitem (Swift 5)
Randomizing Node Movement Duration
Value of Optional Type 'Nsurl' Not Unwrapped; Did You Mean to Use '!' or ''
Swift Uikit Dynamics Add Collision Boundary. After Rotation Does Not Work Correctly
Toggle Selectedrange Attributes in Uitextview
Loading Uiviewcontroller from Instantiateviewcontrollerwithidentifier - Nil Outlets
How to Know When to Call a Closure with () or Without Parentheses in Handler:
Check If an Int Value Is Greater or Equal to Another Int? Value
How to Ensure Make Sure I'm Not Accessing Data Until It's Loaded In
Making Gradient Background in Swift - the Right Way
How to Pass a Local Function to Another Object During Init
How to Deal with Commas When Writing Objects to CSV in Swift
Check Whether the Arreferenceimage Is No Longer Visible in the Camera's View
Receiving Data from Nsinputstream in Swift
How to Procedurally Draw Rectangle/Lines in Swift Using Cgcontext