I'm developing a desktop Cocoa application. In the app I have a view-based NSOutlineView binded to an NSTreeController:
The NSTreeController is in entity mode and driven by Core Data. Everything works as expected until the underlaying model graph changes. Whenever a new object inserted into the registered NSManagedObjectContext the NSTreeController refresh its content and the binded NSOutlineView shows the result properly. The content of the controller sorted by "title" with an NSSortDescriptor and I set this sorting during the application startup. The only drawback is that the selectionIndexPath doesn't change even if the preserve selection box is checked in the NSTreeController's preferences. I want to keep the selection on the object that was selected before the new node appeared in the tree.
I've subclassed NSTreeController to debug what's happening with the selection during the change of object graph. I can see that the NSTreeController changes it's content via KVO but the setContent:
method doesn't invoked. Than the setSelectionIndexPaths:
called via the NSTreeControllerTreeNode KVO but the parameter contains the previous indexPath.
So, to be clear:
- Top Level 1
- Folder 1-1
- Folder 1-2
- Top Level 2
- Folder 2-1
- *Folder 2-3 <== Selected
- Folder 2-4
In the initial stage the "Folder 2-3" selected. Then "Folder 2-2" inserted into the NSManagedObjectContext with [NSEntityDescription insertNewObjectForEntityForName:@"Folder" inManagedObjectContext:managedObjectContext];
:
- Top Level 1
- Folder 1-1
- Folder 1-2
- Top Level 2
- Folder 2-1
- *Folder 2-2 <== Selected
- Folder 2-3
- Folder 2-4
I want to keep the selection on "Folder 2-3", hence I've set the "Preseve selection" but it seems that NSTreeController completely ignore this property or I misunderstood something.
How I can force NSTreeController to keep its selection?
UPDATE1:
Unfortunately none of the mutation methods (insertObject:atArrangedObjectIndexPath:
, insertObjects:atArrangedObjectIndexPaths:
etc.) has ever called in my NSTreeController subclass. I've override most of the factory methods to debug what's going under the hood and that's what I can see when a new managed object inserted into the context:
-[FoldersTreeController observeValueForKeyPath:ofObject:change:context:] // Content observer, registered with: [self addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew context:nil]
-[FoldersTreeController setSelectionIndexPaths:]
-[FoldersTreeController selectedNodes]
-[FoldersTreeController selectedNodes]
The FoldersTreeController is in entity mode and binded to the managedObjectContext of Application delegate. I have a root entity called "Folders" and it has a property called "children". It's a to-many relationship to an other entity called Subfolders. The Subfolders entity is a subclass of Folders, so it has the same properties as its parent. As you can see on the first attached screenshot the NSTreeController's entity has been set to the Folders entity and it's working as expected. Whenever I insert a new Subfolder into the managedObjectContext it appears in the tree under the proper Folder (as a subnode, sorted by NSSortDescriptor binded to the NSTreeController), but none of the NSTreeController mutation methods are called and if the newly inserted subfolder appears earlier in the list it pulls down everything but the selection remains in the same position.
I can see that the setContent:
method is called during the application launch, but that's all. It seems that NSTreeController observe the root nodes (Folders) and reflect model changes somehow via KVO. (So, when I create a new Subfolder and add it to its parent with [folder addChildrenObject:subfolder]
it's appearing in the tree, but none of the tree mutation methods are invoked.)
Unfortunately I cannot use the NSTreeController mutation methods directly (add:
, addChild:
, insert:
, insertChild:
) because the real applicataion updates the models in a background thread. The background thread uses its own managedObjectContext and merge the changes in batches with mergeChangesFromContextDidSaveNotification
. It makes me crazy, because everything is working fine expect the NSOutlineView's selection. When I bunch of Subfolders merged into the main managedObjectContext from the background thread the tree updates itself, but I lost the selection from the object that was selected before the merge.
Update2:
I've prepared a small sample to demonstrate the issue: http://cl.ly/3k371n0c250P
- Expand "Folder 1" then select Select "Subfolder 9999"
- Press "New subfolder". It will create 50 subfolder in the background operation with batches.
- As you can see, the selection will be lost from "Subfolder 9999" even if its saved before the content change in MyTreeController.m