NSOutlineView and Core Data 1 → Many Relationships - swift

I have a Core Data model that supports a 1 → Many relationship (1 Folder to many Phrases). At the moment I'm just displaying the Phrases on a flat NSTableView using Core Data Bindings to a NSArrayController to glue everything together - this is working happily.
I'm trying to experiment with an NSOutlineView to achieve the same result but showing the folders as well. I've tried a similar binding structure that I'm already using with the NSTableView but I'm not making any headway. What steps should I take to move from a flat NSTableView to a NSOutlineView with 'depth'?
Here's my MOM:
Top level folders, bottom level phrases. No nesting. I'm not an animal.
NSTreeController
I have a NSTreeController bound to the array controller for arrangedObjects on Controller Content
It is set to 'Entity Name' mode with an entity name of Folder (this feels wrong)
phrases is set to the children key path
Prepares Content is true
It's mOC is set to a valid mOC
NSOutlineView
Outline View Content is bound to the Tree Controller using arrangedObjects as the Key and string as the Key Path
The Phrase NSManagedObject
has an extension called 'phrases' which returns an empty set, since it'll never have children.
When I do this, I get this as a result:
[APPNAME.Folder copyWithZone:]:
The tutorial that got me this far had nested objects and only one type of entity, where as I have two. What do I need to have an accurate representation of my core data objects in a NSOutlineView?

NSTreeController and NSOutlineView work in the same way as NSTableView and NSArrayController. To use NSTreeController you don't need a NSArrayController, you can put the treecontroller in entity mode.
my entities are not called Table View Cell
Remove the column binding, it is for cell based ouline views. Bind the content of the outline view to the tree controller and bind the text fields in the Table View Cells to the Table Cell View.
The tutorial that got me this far had nested objects and only one type of entity, where as I have two.
This is a problem because NSTreeController has only one childrenKeyPath and the textfield in the outline view can only bind with one key. As far as I know there are two workarounds.
Solution 1: Create a subclass of NSTreeController and override
- (NSString *)childrenKeyPathForNode:(NSTreeNode *)node
the managed object is node.representedObject.
Implement the delegate method outlineView:viewForTableColumn:item: and use a different view for each entity.
Solution 2: Create NSManagedObject subclasses and implement calculated properties for children and display so all entities answer to the same keys.
I think this solution feels wrong, managed objects shouldn't contain code for views, but it is very easy to implement if you already have NSManagedObject subclasses.

Related

How do i update CoreData after a row movement in a UITableView?

I'm displaying a bunch of data in a UITableView, the source of my data is from an NSSet from a Core-Data many-to-many relationship. If a user moves a row inside the table view i want it to have a permanent affect inside Core-Data. So that when the table view is reloaded you can see the changes, and the NSSet is re-ordered the way the user left it after moving around the rows. I'm wondering what approach would be best for this. Thanks!
If you specify the relationship as ordered, it will be represented as NSOrderedSet instead of NSSet. As its name implies, an ordered set allows you to indicate the order of the items it contains. The best route to manipulating this to reflect your user's chosen sequence is the method mutableOrderedSetValue(forKey:) which will given you a mutable ordered set (NSMutableOrderedSet) that you can re-order using various add, insert, move, replace, remove methods.

How would you create a table view that, using a segue, has another table view inside each cell using core data?

So, I am creating this app that involves the creation of routines in a table view. Once you create this routine in the table view, you are then given the option to look inside this routine. Once you look inside it, you are presented with another table view that holds a set of tasks each, all of these created by the user. I am using Core Data, and I'm having problems in my data model assigning the task to the routine that it's in. What happens in the simulator is that all of the tasks that I've created can be seen in all of the routines. This is not the behavior that I want at all, what I am looking for is for each routine to have its own individual tasks. How would you do this? Please keep in mind that I am using Core Data.
First you need to define the relationship between the two entities. In the model editor, ctrl-drag from the Routine entity to the Task entity. This will create a new relationship between the two entities (indicated by the line):
Select the newRelationship in the Routine entity, and rename it to "tasks"(in the panel on the right), and change the "Type" to "To Many" - each Routine can have many Tasks. Likewise rename the newRelationship in the Task entity to "routine" (you should also decide whether each Task can belong to many Routines, or only one, and change the "Type" accordingly; I've assumed it will be "To One"):
The "Type" is indicated by the arrowhead - single for "To One" and double for "To Many". (You should also look through the CoreData docs and decide what "delete rule" you want.)
That is the relationship is defined. To simplify populating the relationship, it's worth generating NSManagedObject subclasses for each of your entities. Then you can access their properties (and relationships) using dot notation. The Model Editor can generate the subclass definitions for you - in the Editor menu, "Create NSManagedObject subclass":
Follow through the various dialogs and a .swift file will be created for each Entity. Now to populate the relationship is easy - if you have a Routine object, say myRoutine, and a Task object called myTask, just use
myTask.routine = myRoutine
This will automatically set the relationship both ways - from myRoutine to myTask and vice versa.
Now, define a property ("myRoutine") in your second view controller of type Routine. When you segue from your first view controller, set the value of myRoutine to the chosen Routine. Then, after creating any new Task objects, set their routine to myRoutine, as above. Or if you are only displaying the tasks for the chosen routine, you can use myRoutine.tasks (which is a NSSet of all the Task objects for the chosen Routine) as the datasource for the table view in your second view controller.

How do I manage models & views in an iPhone app?

I have a bunch of model objects that inherit from NSObject (Result). And I have a bunch of view objects that inherit from UIView (ResultView). They exist in a 1:1 relationship (one model for one view). In my controller I have an two arrays: one for Result objects and one for ResultView objects. The controller displays many of these result views and they can be added/deleted/reordered. Trying to keep 2 arrays in sync (results & resultViews) isn't working out. How should I approach this problem?
I'm considering initializing a view object with a model object (eg: an initWithResult: in my ResultView class and then retain a pointer to the Result object in the ResultView). Then I could do something like ResultView.result to access model data. Is there a better solution? This would break MVC, wouldn't it?
Unless you're trying to persist these model objects in a DB or something similar, I would put a view property on the model objects. If you don't have to create the view for any reason, then just nil it out to save on memory.
Does it break MVC? I guess. But if your model objects will always have a view associated with them, it starts to go into the blurry line area. No programming god will send you to hell for breaking the standard a little bit.
Do what's clean, optimal, and easiest for another programmer to understand when looking at your code.
Ok, if I understand your question correctly, the model objects are persistent while the views are dynamically created/deleted. You are relying implicitly on the index of the two arrays to achieve the mapping of two parts. One simple way is to add a "model object id" in your view class. Then you can easily reference to the correct model object in the view.

What is the best practice to reference model objects in views with Core Data?

I have an NSManagedObject that described an animal, it has the attributes "name", "species", "scientificName" etc. I display all animals in a UITableView and managed the NSManagedObjects with an NSFetchedResultsController.
To provide my table view cells with the information necessary to render the content so far I just weakly referenced them to my NSManagedObject describing the animal. However, sometimes the NSManagedObject should get faulted and when the table view cells are redrawn, they re access the properties of the object and thus prevent the fault.
What are the best practices to provide view objects with information when using core data as data source? If possible I would like to avoid copying all attributes of the NSManagedObject over to the table view cell.
I believe it is a good practice to clearly separate the Model, View and Controller layers. Especially making sure the Views are not holding Model state. Holding on to a NSManagedObject holding on to a Model object. Copying some data is unavoidable.
I usually implement a method for "painting" the View with the Model data. Something like this in the UITableViewCell subclass:
-(void)configureWithAnimal:(NSManagedObject*)animal {
self.nameLabel.text = [animal valueForKey:#"name"];
self.speciesLabel.text = [animal valueForKey:#"species"];
// Etc.
}
This way it is a single line of code in the UITableViewController subclass to setup the cell, independently of newly created or reused cells. And if many tables wants to reuse the custom cell then not all f them need to reimplement the code to seeing up the cell.

iPhone: NSFetchedResultsController for each Entity?

I have three entities with one-to-many relationships (Book <--->> Page <--->> Text)
I want to use one table view to present Book.titles, one table view for Page.no and one view to show the Text when clicking on a Page.no.
Do I need to setup up a fetchedResultsController for each entity or can I get access to a Text object
using the Book entity - Book.pages... etc?
If I understand your question correctly, you only need one fetched results controller and CoreData will do the rest.
So, you fill an NSFetchedResultsController with all the books you want to display and present them with a UITableViewController subclass. Then when the user selects one, you pass this book on to another UITableViewController subclass which uses book.pages to get and display all the pages in that book. This idea is then repeated to show the text entities.
HTH
PS - If you aren't already, you may find it useful to use xCode's Managed Object Class generator to ensure book.pages and pages.texts are correctly set up. To use this, open your .xcdatamodel file, highlight an entity and choose File->New File and choose Managed Object Class and then follow the steps.
You can access all objects via the relationships but depending on how complex your tableviews are, it may get tricky to manage the datasource methods. It's doable though!
For instance, if you decide to fetch the books, provided you have a reference to the instance you get all pages and put them in an Array with:
Book *bookItem = [self.frc objectAtIndexPath:indexPath];
NSArray *dataSource = [[book objectForKey:#"pages"] allObjets];
Another option is to create an abstract class and make it a parent of all 3 entities, then fetch using this class which will give you an array of all books, pages and text. From there you can test for the class and populate the tables accordingly.