CoreData and NSTreeController - objective-c

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

Related

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.

Elegantly add object to arraycontroller, which itself is linked to another arraycontroller?

I'm new to OS X programming but generally liking it. I have the following problem:
I have two core data entities linked through a one-to-many with their respective arraycontrollers (Stock Controller and Price History Controller, where the latter controller is bound to the Stock Controller, with Controller Key = selection and Model Key Path = priceHistory, which is the relationship that links the stock entity to the PriceDataPoint entity, controlled by the Price History Controller.
This all works like magic in my UI, where I can select stocks and add/remove price points to each one when it is selected. However, I need to be able to do this programmatically as well.
If I simply call [stockController add:self] the UI updates with new objects with the correct default values, linked to the selected stock -- even though 'self' is not the correct class/entity. This is one point of confusion for me, which I don't understand (I understand that the Stock Controller knows about the selected stock through the KVO binding and would likely set the relationship as required, but I don't get how it 'casts' 'self', which is a fairly random class into the object type required (a plain NSManagedObject)? Secondly, if I do this, how do I get a reference back to that object so I can edit its values?
More importantly, however, if I then follow the Apple examples, create a new NSManagedObject through NSEntityDescription:insertNewObjectForEntity: and use [stockController addObject:Newly created Object] I can write the values I want before adding it but the relationship to the 'parent' stock is not set by the addObjects: method. I am sure I can figure out how to write this, too but with everything else in Cocoa being so elegant this just feels odd so I am hoping that someone here can clear this up very quickly and point me to an elegant way of doing it.
First, let's dispel the magic by stating Core Data is built to work with the Cocoa Bindings mechanism (which gives you array controllers, among other things) and is built atop Key Value Coding / Key Value Observing mechanisms. The Core Data documentation clearly states you should have a good background in these technologies to understand how / why things are working.
For your first issue, you said you're confused by the -add: method of NSArrayController. Have you looked at the signature/prototype? The argument (where you pass self) is (id)sender, which is the hallmark of a basic action (see "target/action" in the docs). You can pass self, some other object, or even nil if you want. The argument is NOT the object you wish to add to the collection managed by the array controller.
For your second issue, you're on the right track if you want to customize things or keep a reference to the newly-inserted object. You can use the -addObject: method (which does take the object to add as its argument) but you'll have to tell the array controller to -fetch: (another action w/sender as argument) in order to refresh the controller AND anything bound to its contents (such as a table/outline).

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.

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

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.

Programmatical Creation of NSMappingModel

I want to programmatically (without Lightweight Migration) create a mapping model between two models that are exactly the same, except one of the entities (there are a bunch of entities) has different attributes. Let's call this entity "Person". And let's say the destination model has
1) added a new attribute called "address"
2) deleted an attribute called "eyeColor"
3) kept (i.e. not done anything with) an attribute called "name"
How would you create an NSMappingModel between these models programmatically? I happen to have some explicit questions that might help me do this by myself:
Q1) Do I have to create NSEntityMapping objects for all of the entities other than "Person", even if they remain unchanged?
Q2) How do I deal with the "address" attribute in "Person", which is a new one being created? Should I create an NSPropertyMapping for that somehow, that turns nothing into something ("address")?
Q3) How do I deal with the "name" attribute in "Person"? Do I have to create an NSPropertyMapping for that, even though it simply stays the same?
Q4) For the NSEntityMapping corresponding to "Person", is not creating any NSPropertyMapping for "eyeColor" a proper way to get it deleted? Or should I create an NSPropertyMapping for "eyeColor"? If yes, how would this object be created, i.e. what would determine that its purpose is to get rid of "eyeColor"?
Thank you in advance, and I apologize not being able to answer these questions myself, as the documenation really has no good example of how to create NSMappingModels programmatically. Note again that I'm not allowed to use Lightweight Migration. I must do this manually.
I've always used the automatic mapping feature of Xcode but it seems to me you can learn a lot from that as well. Make a model of (parts of) the source model, add a version, modify it to reflect the destination model and generate a Mapping Model (menu Design >> Mapping Model). If you then control-click the .xcmappingmodel Xcode has generated for you and tell the Finder to show you the contents of the package, you'll find an XML file inside that lists all the mappings. You can use the xml as a guide to help you recreate the process in code. Good luck.