Placing items from a Core Data entity into an NSOutlineView programmatically? - objective-c

Sorry if this seems like a silly question - I am an amateur when it comes to Objective-C and Cocoa and even less knowledgable when it comes to Core Data usage.
So here's the situation: I have an NSOutlineView that I've already populated with a few items manually with an NSTreeController. What I need to do now is take the items in one of my Core Data entities and append them to the NSOutlineView's current contents.
Obviously this is beyond the abilities of bindings, so it will need to be done programmatically. What should I do? I assume that I need to do a fetch and then iterate through the returned items, adding each to the outline view. Is this correct? If so, would anybody be able to show an example of how this is done?
Thanks!

Create an NSFetchRequest with an NSPredicate that gets only those whose "parent" is nil (the root/top-level objects). Sort them by some attribute that makes sense (as the fetch results will be an unordered collection - an NSSet). Then implement the NSOutlineViewDataSource to mix/mingle the information as you see fit as it's provided to the outline.
Caution: It's best to cache your results, observing the context for changes and refreshing the cache on each change.

Related

How to pass the model to a Controller in Core Data

Say I have a Core Data class called RecipeBook which has a property (relationship) called recipes, a NSSet of Recipe Objects.
I display the RecipeBooks on a UITableView and when the user taps on a cell, it should display the Recipes on another UITableViewController.
What should I pass as the model to this last UITableViewController:
a context and a fetchRequest
or the NSSet of Recipe objects?
If there's a change to the db will the NSSet "automagically" update?
From your description I'd probably pass the selected RecipeBook instance. From that I can (presumably) get all the recipes contained in the book and display them in the table. That assumes that a relationship exists from RecipeBook to Recipe which-- based on your description-- should be true. If I need to do any other work with the data store, I could ask the RecipeBook for its managed object context and work with that.
No NSSet of fetched objects is going to update automatically. But the relationship from a RecipeBook to its Recipes will update any time a recipe is added or removed from the recipe book.
And finally-- passing any of this directly to a UITableView doesn't make a lot of sense. Apple's iOS frameworks are designed with MVC in mind, and going against that will make things a lot harder than they need to be. If you have a UITableViewController, you could pass your model objects to that.
Generally, you shouldn't pass "model" objects to view objects.
My favorite way to think about it is that views are actually another form of a model (think of them both as simply representations of data). The controller's job is to ensure that neither representation needs to know anything about how the other stores it's representation.
So basically, your controller will be your data delegate, and is responsible for properly populating table cells with it's own references to your core data models.
The automagically question depends on your core data setup, but usually the answer is yes.
By default, CD uses key-value coding, which simply lets you access properties with valueForKey:. More advanced setups involve having Xcode generate classes for you, in which case a few mouse clicks get you "dot notation" accessor methods regenerated from an updated model.

Is using protocol and delegate to create a Control like UITableView the optimized one?

The UITableView can be used to create a list view if the at least the UITableViewDataSource is adopted by the relevant class. I have the below questions:
Why is it designed in such a way that based on the section and row , the controls are created through data source methods and given back to the UITableView instance. Why not provide all these information in UITableView instance with out using the UITableViewDataSource. What difference is it going to make?
EDIT1:
#hermann and #JOhn: You have mentioned that it breaks the MVC pattern. Let us assume I am creating a custom UITableView like control myself. I design it in such a way that I do not pass the data directly to the UITableView but instead I pass the relevant subviews that needs to be added in the rows and section and their relavant headers alone. I think this will not break the MVC..Am I correct? But still it has the problem that the current UITableView implementation style solves..the ability to reuse controls and images instead of bloating the memory usage.
First, doing so allows to stick on the MVC pattern. A UI object, meaning a view, should never directly communitcate with the model, wich is the business data. Plus it should not perform any business logic, which belongs to the controller.
Second, it is more flexible without the need of subclassing the UITableView object.
Third, the full concept is quite efficient from a performance and memory management point of view. Instead of loading all data upfront, that may be displied within a table, it can be fetched or calculated or whatever on a just in time "need to know now" basis.
Plus data containers, especially memory consuming once like images, can be released as soon as the data has been provided using the delegate/data source methods.
Of course, a subclass of UITableView could do the same in principle. I just doubt this would result in more maintainable code or even save any time or work resprectively.
If you don't want to stick with MVC, then feel free to subclassing the table view and hand all its data over in its init method or enable the table view subclass to load all that data from webservices or data bases or wherever it comes from.
And when you run into problems and get back to us searching for guidance, then be prepared for some nasty replies like "Why don't you stick to the established best practices or the MVC pattern?" ect.
In the event that your very table just displays some rather static values, such as if it just acts as menu for further navigation drill down etc. then this concept may look a bit rediculous. But it does not hurt either. And when it comes to more complex tasks of data providing then it certainly has its advantages.
Give it a chance. Give it a try!
For MVC you want to try to clearly separate what the model, controller, and views are responsible for doing. By allowing the view (tableview) to ask for the data from a delegate, the view can be create extremely generalized.
Using the delegate pattern, the controller can "stage" the data in anyway that is wants and then give it to tableView as the tableView needs it. The tableView doesn't care where the data comes, how it was transformed, what ADT is used, nothing. It is complete ignorant to where or what the data is. All it knows it should show this string at this location for this section and row.

CoreData and NSTreeController

At the moment I'm sticking around with the nstreecontroller that is backend with core data. My problem is that no rearrange get triggered if I edit a field or add a new row.
Structure:
NSTreeController that is configured with prepare content ON and uses lazy fetching ON. The controller is bound to appDelegate.managedObjectContext and to appDelegate.mySortDescriptor (which is a function that returns an array with a nssortdescriptor inside).
Outlineview that has his columns bound to treecontroller.arrangedObjects.name. The Outlineview is also in SourceList mode.
An entity called "Item" that has a one-to-many relationship (children) to Item and a one-to-one relationship (parent) to Item. And of course there is a field called "name" that holds the displayvalue.
two storages. One is only hold in memory and the default one. The temporary storage is used to set up the root entities (I don't want them to get stored because they're just "visual")
Until here everything is fine except for:
On launch the sortdescriptor get read in but not executed (I've to call myself the rearrange function)
no modification on the core data triggers the rearrange method
I already tried to solve the problem at me own with following results:
Removing "Uses lazy fetch" will solve the problem o_O but who wants that?!
Calling rearrange in outlineView:setObjectValue:forTableColumn:byItem: (outlineview datasource) will solve the problem. But I do not really understand why this gets called (I'm using coredata, so it shouldn't get called?). On the other hand, calling rearrange to often messes up the sorting again (Ex. if you save a cell which hasn't changed his value)
That's the reason why I told myself that you've to face the fact that you're doing something wrong :)
What I actually want to have is the following:
CoreData backend
Outlineview is always sorted (the user cannot change it)
Skipping the save of the root objects
Less work is always welcome
thanks a lot and best regards, matthieu riolo
PS: Hints are appreciated too

Why is an NSArrayController "losing" its sort after an object is inserted?

I have an NSArrayController bound to a property of an NSManagedObject subclass. The subclass is automatically generated by mogenerator (which creates a set property for the relationship).
The NSArrayController is bound to network.posts where 'posts' is a 1-M relationship. Note I am binding to 'posts' rather than the 'postsSet' mutableSet accessor generated by mogenerator.
The set represents a relationship with another entity. The array controller has a sort descriptor set and, when the window it is associated with opens, data is displayed in the correct sort order.
Then I add a new object by instantiating an entity and then adding it to the relationship.
The NSArrayController is correctly observing this change and the new object appears in arrangedObjects however, after the insert the sort order of arrangedObjects is lost and the records appear in a different order.
I have verified that the sort descriptor is still set correctly. The NSArrayController has autoRearrangeContent=YES. I've even tried manually calling -rearrangeObjects after the insert but the sort order remains wrong.
If I close the window and re-open the newly instantiated NSArrayController has the data with the correct sort order again. Until I do another insert.
My experience has been that NSArrayController has automatically kept the correct sort order when objects are added/removed but maybe that was a lucky coincidence?
I can't find any description of the correct behaviour in the Apple document so I've no idea what to expect, what might be going wrong (if anything), or - in either case - what I should do about it.
1) Given a sortDescriptor should NSArrayController be keeping arrangedObjects sorted after objects are inserted/removed from the underlying collection?
2) If so, what might prevent this from happening?
3) And, if not, what is the correct way to keep arrangedObjects sorted?
I'd be grateful for any help. It's not easy to provide useful source code in this situation since, in principle, there isn't any. But I'm happy to clarify and answer any follow-up questions.
Turn off lazy fetching. Not sure the reason but when the controller is being lazy it doesn't resort things when they are edited or added.
By what are you sorting? Core data does not support ordered collections. To preserve any kind of order, you have to add some sort of attribute (pun intended) and sort by it.
Behind the scenes, CD is using NSSet/NSMutableSet - not NSArray/NSMutableArray - for collections. That your objects come up in the same order more than once is entirely due to caching, not by CD maintaining your collection's order.
Update for Lion (10.7)
With regard to my "does not support ordered collections" statement: If you're targeting 10.7 and above in your application, [NSManagedObject now gives you ordered relationships.][1] Use -mutableOrderedSetValueForKey: and -mutableOrderedSetValueForKey: to set and retrieve NSOrderedSets. Yay!
Perhaps instead of binding the NSArrayController to your attributesSet (generated by mogenerator), you should instead bind to the underlying dynamic attributes property itself, which is created by Core Data framework? attributes stands for your relationship name.

Proposed solution to NSTreeController displaying duplicate entities

As many of you may know, an NSTreeController bound to an outline view can display duplicates while presenting core data entities.
A temporary solution is to add 'parent == nil' to the predicates, but this only returns parent entities. If, for instance, a user is searching for a sub-entity, the requested sub-entity won't be displayed.
A (proposed) solution is to subclass NSTreeController and add a NSMutableSet variable, which keeps track of entities that are currently being displayed. This variable should be alloced on init, and released on dealloc.
When "fetchWithRequest:merge:error:" is called, the set should be emptied (I'm not sure whether this would be more efficient than releasing it and allocating it again). Everytime an entity is going to be added to display, check if the set contains it. If it doesn't, add it. Otherwise, find which is closer to the root (which is the subentity) and either skip it if its the subentity, or swap it with the previously included one.
I think there should be relatively little impact on performance (considering NSSet uses hashing). The problem I'm having is finding the correct method to override to add this behavior. Specifically, where NSTreeController processes fetched entities after "fetchWithRequest:merge:error:" is called.
Is it fair to say you're really looking for a way to filter the tree with a search term without losing the tree structure? The inherent problem (beyond forcing the tree controller to include the parent nodes of a search match) is that the parents may or may not actually match the search result, so it's confusing to display them.
I think yours is more a problem of UI, isn't it? In that case, the best approach (and one I've seen many well-known companies and independent developers take) is to display search results in a plain table. This way the results can be sorted by various attributes and you don't have to disable drag and drop in the outline view in search mode (to avoid the user trying to change the tree structure when only part of the tree is displayed out of context).
Expanding on Joshua's answer, I was able to implement Search Functionality into my own NSOutlineView, however it was limited to the root/parent objects in the view.
I think (like Joshua said) if you wanted to filter all objects you would have to display the results in a NSTableView.