Accessing Core Data Child Objects in Nested SwiftUI List - core-data

My end goal is to create a SwiftUI List with children. I have sections (parent) that contain projects (children), and the sections will expand and collapse their list of projects.
I have this built with an NSOutlineView using Realm, but now I'm trying to build it with SwiftUI and Core Data. *GULP* : )
I'm not very experienced with Core Data, but I think I have my data set up right. I have a Section entity and a Project entity, and the Section has a projects attribute that has a to-many relationship.
So, I presume (incorrectly as shown below) section.projects is a collection of Project objects.
For simplicity, I'm just trying to nest to the two inside a List (I'll worry about the DisclosureGroup stuff later):
List{
ForEach(sections, id: \.recordName){ section in
Text(section.name)
ForEach(section.projects, id: \.recordName){ project in //<-- ERROR
Text(project.name)
}
}
}
The line marked above has two errors that seems to suggest I don't have an array of objects to work with in section.projects:
Referencing initializer 'init(_:id:content:)' on 'ForEach' requires that 'NSObject' conform to 'RandomAccessCollection'
Value of optional type 'NSOrderedSet?' must be unwrapped to a value of type 'NSOrderedSet'
If section.projects is an NSOrderedSet, why can't I iterate over it?

I figured this out with the help of this article: https://www.hackingwithswift.com/books/ios-swiftui/one-to-many-relationships-with-core-data-swiftui-and-fetchrequest
I had to change my Core Data entities to have their Codegen setting be Manual/None (in the .xcdatamodeld editor in Xcode). I then select my entities and go to Editor > Create NSManagedObject Subclass... which created a bunch of files that reflect my Core Data models.
Then inside the Section+CoreDataProperties.swift file, I added a computed property to give me access to my projects (sorted by name):
public var projectArray: [Project] {
let set = projects as? Set<Project> ?? []
return set.sorted {
$0.wrappedName < $1.wrappedName
}
}
And then, for convenience, inside Project+CoreDataProperties.swift I added this:
public var wrappedName: String{
name ?? "Unknown Name"
}
...which allows my computed property to sort my projects by name (since Core Data treats strings as optionals by default).
As a result, I was able to iterate over my nested data like this:
ForEach(sections, id: \.self) { section in
Text(section.wrappedName)
ForEach(section.projectArray, id: \.self) { project in
Text(project.wrappedName)
}
}

Related

Implementing ForEach.onMove() with Core Data

I am trying to implement ForEach.onMove using Core Data. I have two entities, Parent with a To-Many relationship to Child, with the relationship marked as Ordered. The view is something like this:
struct ParentView : View {
#ObservedObject var parent : Parent
var body : some View {
List {
ForEach($parent.children! as! [Child]) { child in
ChildView(child)
}.onMove {
parent.managedObjectContext!.performAndWait { indices, to in
(parent.children! as! NSMutableOrderedSet).moveObjects(at: indices, to: to)
try! parent.managedObjectContext!.save()
parent.objectWillChange.send()
}
}
}
}
}
Results are:
No errors of any kind.
During debug of the onMove function, I can see that the items are re-ordered as required
The managedObjectContext.updatedObjects is empty at the same debug step, before the call to save()
When reloading the app, the re-ordering is obviously not saved (apparently because the updatedObjects set was empty at #3.)
What am I doing wrong? How can I make the MOC realize the re-ordering change?
I fixed this, apparently with a simple solution. I am posting this for future generations of google searchers. :D
The problem is with this line:
(parent.children! as! NSMutableOrderedSet).moveObjects(at: indices, to: to)
Apparently, taking the immutable version and making it mutable works, but doesn't update the managed object. This works:
parent.mutableOrderedSetValue(forKey: "children").moveObjects(at: indices, to: to)

How do you get SwiftUI Previews to work when Codegen is in ClassDefinition or Category/Extension?

I have a view using core data and xcdatamodeld file that contains the definition for an Item struct. If I use Xcode to generate the files for Item and manually manage them, preview works fine. However, when I use Codegen in either of the other formations, I get errors saying that the entire struct is undefined. This prevents the previews from working.
Code:
struct ArchiveView: View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#FetchRequest(entity: Item.entity(), sortDescriptors: []) var fetchedResults: FetchedResults<Item>
var body: some View {
return NavigationView {
List(fetchedResults, id: \.self ) { (fetchedResult: Item) in
return PurchaseView(name: fetchedResults.name price: fetchedResults.price, purchaseDate: fetchedResults.date)
}
}.navigationBarTitle("Order")
}
}
Both the Item+CoreDataClass and Item+CoreDataProperties are missing since they are automatically generated by xcode.
I am now using manual Codegen to be able to see the previews, but am curious whether I could use the other options. How can I use Class Defintion Codegen for the core data files and still be able to use SwiftUI previews?
After some testing, something appears to be wrong with the xcode code generator itself. Whenever I change the code generator to anything other than Class Definition at least once, this breaks the linking with previews.
The fix was to create a new project and copy over the old files.

Haxe - Why can I not access a child's attribute without getting an error that the parent does not have the given attribute?

I've recently been getting into Haxe and just started to use HaxeFlixel to load a Tiled .TMX file.
I am creating a TiledMap object and passing it the TMX file path, then I want to iterate over the layers in that object to add them to the game scene. However when I try to access .tileArray (which is a property of TiledTileLayer) I get the following error :-
flixel.addons.editors.tiled.TiledLayer has no field tileArray
Here is the code:
package;
import flixel.FlxState;
import flixel.tile.FlxTilemap;
import flixel.addons.editors.tiled.TiledMap;
import openfl.Assets;
class PlayState extends FlxState
{
private var _tiled_map:TiledMap;
override public function create():Void
{
_tiled_map = new TiledMap("assets/data/Map1.tmx");
for(layer in _tiled_map.layers){
var layerData:Array<Int> = layer.tileArray;
}
super.create();
}
override public function update(elapsed:Float):Void
{
super.update(elapsed);
}
}
I've found the following example - http://coinflipstudios.com/devblog/?p=182 which seems to work fine for people.
So I wanted to check whether the layer object was a TiledTileLayer as it should be, or TiledLayer, with the following:
trace(Type.typeof(layer));
Which sure enough yields:
PlayState.hx:24: TClass([class TiledTileLayer])
So if it is a TiledTileLayer which has the field tileArray why is it moaning?
I had a look at the source code (https://github.com/HaxeFlixel/flixel-addons/blob/dev/flixel/addons/editors/tiled/TiledMap.hx#L135) and TiledTileLayer inherits from TiledLayer. Layers is an array of type TiledLayer, so I think this is why it is moaning. I can clearly see that the array is storing child objects of TiledLayer, but as soon as I access any props/methods of those children, it complains that the parent does not have that field? Very confusing!
To run I'm using this command: C:\HaxeToolkit\haxe\haxelib.exe run lime test flash -debug -Dfdb
Thank you!
So if it is a TiledTileLayer which has the field tileArray why is it moaning?
It may be a TiledTileLayer in this case, but that may not always be the case. layers is an Array<TileLayer> after all, so it could be a TiledObjectLayer or a TiledImageLayer as well (which don't have a tileArray field). This can nicely be seen in the code you linked. The concrete type can only be known at runtime, but the error you get happens at compile-time.
If you know for sure there won't be any object or image layers, you can just cast it to a TiledTileLayer. However, just to be safe, it's good practice to check the type beforehand anyway:
for (layer in _tiled_map.layers) {
if (Std.is(layer, TiledTileLayer)) {
var tileLayer:TiledTileLayer = cast layer;
var layerData:Array<Int> = tileLayer.tileArray;
}
}
It works without this for the tutorial you linked because it was made for an older version of flixel-addons.

ektorp / CouchDB mix HashMap and Annotations

In jcouchdb I used to extend BaseDocument and then, in a transparent manner, mix Annotations and not declared fields.
Example:
import org.jcouchdb.document.BaseDocument;
public class SiteDocument extends BaseDocument {
private String site;
#org.svenson.JSONProperty(value = "site", ignoreIfNull = true)
public String getSite() {
return site;
}
public void setSite(String name) {
site = name;
}
}
and then use it:
// Create a SiteDocument
SiteDocument site2 = new SiteDocument();
site2.setProperty("site", "http://www.starckoverflow.com/index.html");
// Set value using setSite
site2.setSite("www.stackoverflow.com");
// and using setProperty
site2.setProperty("description", "Questions & Answers");
db.createOrUpdateDocument(site2);
Where I use both a document field (site) that is defined via annotation and a property field (description) not defined, both get serialized when I save document.
This is convenient for me since I can work with semi-structured documents.
When I try to do the same with Ektorp I have documents using annotations and Documents using HashMap BUT I couldn't find an easy way of getting the mix of both (I've tried using my own serializers but this seems to much work for something that I get for free in jcouchdb). Also tried to annotate a HashMap field but then is serialized as an object and I get the fields automatically saved BUT inside an object with the name of the HashMap field.
Is it possible to do (easily/for free) using Ektorp?
It is definitely possible. You have two options:
Base your class on org.ektorp.support.OpenCouchDbDocument
Annotate the you class with #JsonAnySetter and #JsonAnyGetter. Red more here: http://wiki.fasterxml.com/JacksonFeatureAnyGetter

add<Key>Object vs insertNewObjectForEntityForName Core Data Relationships

Hi,
Although I have a lot of experience in database development, I'm having a hard time conceptualizing linking relationships in Core Data. As I understand it, the many relationship is an NSSet attached to the one file. After reading the documentation, I've understood part of it and gotten it to work in the first import in my code below.
I have a data model into which I perform two separate imports using the XMLParser. The first import loads Events and Categories from the same XML file within the same import like so:
if (thisTagIsForOneTable) {
// Insert object for the one-entity (Events)
self.eventsObject = [NSEntityDescription insertNewObjectForEntityForName:#"Events" inManagedObjectContext:xmlManagedObjectContext];
return;
}
if (thisTagIsForManyTable) {
// Insert object for many-entity (EventCategories)
self.categoriesObject = [NSEntityDescription insertNewObjectForEntityForName:#"EventCategories" inManagedObjectContext:xmlManagedObjectContext];
return;
}
......
// Set attribute values depending upon whether the tag is for the one-entity or the many-entity.
[self.xxxObject setValue:trimmedString forKey:attributeName];
......
// Set the relationship. This works great!
if (thisTagIsForManyTable) {
NSMutableSet *manyRecordSet = [self.eventsObject mutableSetValueForKey:#"categories"]; // My relationship name.
[manyRecordSet addObject:self.categoriesObject];
}
// Save the context. Voila.
The above works fine. The second import loads EventLocations separately in a different part of the application so I need to set it's to-one relationship to Events. This is where I'm not too sure. Should the steps be?
// Step A) Create (insert) new EventLocations object.
self.eventLocationsObject = [NSEntityDescription insertNewObjectForEntityForName:#"EventLocations" inManagedObjectContext:xmlManagedObjectContext];
// Step B) Locate and get a reference to the the related one-entity's object (Events) by ID? I have a custom class for Events.
// This seems to work but I'm taking a performance hit and getting a compiler warning below. The method returnObjectForEntity does a query and returns an NSManagedObject. Is this correct?
Events *event = (Events *)[self returnObjectForEntity:#"Events" withID:[oneRecordObject valueForKey:#"infIDCode"]];
// Step C) Set the relationship to the Events entity.
if (event) {
[event addLocationsObject:self.eventLocationsObject];
// Compiler warning: incompatible Objective-C types 'struct NSManagedObject *', expected 'struct EventLocations *' when passing argument 1 of 'addLocationsObject:' from distinct Objective-C type
}
// Save the context.
I'm not too sure about steps B and C. Any help would be appreciated. Thanks.
Step B: Please tell us the compiler warning.
To Speed things up you could create a cache (NSDictionary) with all Events and their #"infIDCode" as Keys before starting the import. This would speed things up as long as you can make sure no Events are added/deleted/changed within the import phase.
Step C:
self.eventLocationsObject should probably be declared as EventLocations*.
In general your import should work this way

Resources