Pass data from sheet to insertNewObject in a parent/child relationship - objective-c

I have a problem to insertNewObject in an entity being the child in a parent/child relationship. It's a CoreData app with a local SQLite base. There are 2 entities presented with 2 TableViews on the main window. Using contentSet, the table for the child will only show data relating to the selected parent.
Adding data to the child is done by showing a sheet with a table of items coming from a 3rd entity. User must pick from this table then click Add. On dismissing the sheet, the child table on main window should be updated with a new row. Problem: nothing appears.
Checking the database content with a third-party app, I see that the new data is there but it doesn't appear on the table view because no info on the relationship with parent was stored, so it doesn't know to which parent it relates.
My code is missing info about this but I just don't see how I should program this. In other words: on dismissing sheet, identify which parent is selected and specify this relationship info when inserting the new data in the child. I would appreciate any help.
Here is my code:
// there are 3 entities: Collectors (parent), CollectedItems (child) and Items.
// we call the sheet presenting the Items list to pick from
- (IBAction)showAddItemDialog:(id)sender {
[NSApp beginSheet:addItemDialog
modalForWindow:window
modalDelegate:self
didEndSelector:#selector(didEndAddItemSheet:returnCode:contextInfo:)
contextInfo:nil];
}
// we dismiss the sheet by clicking on Cancel or Add
- (IBAction)dismissAddItemDialog:(id)sender {
[NSApp endSheet:addItemDialog returnCode:([sender tag])];
[addItemDialog orderOut:sender];
}
// depending on clicked button, do nothing or pass selected data
- (void)didEndAddItemSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo (void *)contextInfo {
if (returnCode == 0) {
// do nothing, this is the effect of clicking on Cancel
}
if (returnCode == 1) {
NSString *theName = [[[itemsPickerController selectedObjects] valueForKey:#"itemName"] objectAtIndex:0];
// above, we get the value from the itemName attribute sleected in the Items list
NSLog (#"%#", theName);
// the variable is displayed in the console, so it was correctly selected
[self addNewItemWithName:theName];
}
}
// we use the passed data to create new object (row) in the CollectedItems entity
- (id)addNewItemWithName:(NSString *)theName {
NSEntityDescription *newContent = [NSEntityDescription insertNewObjectForEntityForName:#"CollectedItems" inManagedObjectContext:[self managedObjectContext]];
[newContent setValue:theName forKey:#"collectedItemName"];
// above, we insert a new row in CollectedItems, assigning the value theName to the attribute collectedItemName
return nil;
}

You need to add the relationship between your CollectedItems object you created and its parent the Collectors object. Collectors will have a core data utility method on it (if you have generated the core data managed object classes). It will be called something like addCollectedItemsObject.
Reloading the tableViews should then update with the correct data - as it now knows the relationship.
Even better would be to use NSFetchedResultsController to control the data in your tables so that when you update your data model, the tables will automatically reflect the changes.

And for the record, for those it might help, here is the final working code:
Ensuring the method was generated in the corresponding Collectors class file for (core data managed object classes).
// header file
#class Collecteditems;
#interface Collectors : NSManagedObject { }
#property (nonatomic, retain) NSSet* content;
#end
#interface Collectors (CoreDataGeneratedAccessors)
- (void)addContentObject:(Collecteditems *)value;
#end
// implementation file
#import "Collectors.h"
#import "Collecteditems.h"
#implementation Collectors
#dynamic content;
- (void)addContentObject:(Collecteditems *)value {
NSSet *s = [NSSet setWithObject:value];
[self willChangeValueForKey:#"collecteditems" withSetMutation:NSKeyValueUnionSetMutation usingObjects:s];
[[self primitiveValueForKey:#"collecteditems"] addObject:value];
[self didChangeValueForKey:#"collecteditems" withSetMutation:NSKeyValueMinusSetMutation usingObjects:s];
}
Then adding the addObject method on the NSArrayController controlling the collected items table (see id in original post for reference).
- (id)addNewItemWithName:(NSString *)theName {
NSEntityDescription *newContent = [NSEntityDescription insertNewObjectForEntityForName:#"CollectedItems" inManagedObjectContext:[self managedObjectContext]];
[newContent setValue:theName forKey:#"collectedItemName"];
[collectedItemsController addObject:newContent]; // line added
return nil;
}

Related

NSOutlineView shows only first 2 levels of the NSTreeController hierarchy

I'm populating a NSOUtlineView with a NSTreeController.
The NSTreeController is a 3 levels hierarchy controller (CBMovie, CBDisc and CBEpisode), but only the first 2 levels are displayed in the outline view.
The implementation is the same for all objects: I've implemented methods to specify children, children count and if the object is a leaf. These methods are correctly called for all objects (also for those ones that are not displayed, the grandchildren: CBEpisode).
In the outline View, everything is displayed correctly for the first 2 level. But grandchildren are never displayed, I don't have the option to expand their parent to see them. I can only see CBMovie and CBDiscs.
I'm wondering if there is another setting I'm missing, about how deep the nodes can expand in NSTreeControllers or NSOutlineView configurations.
Below: Implementation in one of the three nodes.
Each node class has different path to its children. This is specified in the -(NSArray*)children method (correctly called).
-(NSArray*)children
{
return [[self Episodes] allObjects];
}
-(int)childrenCount
{
return [[self Episodes] count];
}
-(BOOL)isLeaf
{
return ![[self Episodes] count];
}
Output of logging code. The datasource, the NSTreeController, seems to have the correct structure.
CBMovie
CBDisc
CBEpisode
CBEpisode
CBMovie
CBDisc
CBDisc
CBDisc
CBDisc
CBMovie
CBDisc
CBEpisode
CBEpisode
This is how I populate the NSOutlineView (cell based). I don't use datasource methods, but I'm binding it programmatically.
NSMutableDictionary *bindingOptions = [[NSMutableDictionary alloc] initWithCapacity:2];
if (metadata.valueTransformer) {
[bindingOptions setObject:metadata.valueTransformer forKey:NSValueTransformerNameBindingOption];
}
[bindingOptions setObject:[NSNumber numberWithBool:NO] forKey:NSCreatesSortDescriptorBindingOption];
[bindingOptions setObject:[NSNumber numberWithBool:NO] forKey:NSRaisesForNotApplicableKeysBindingOption];
[newColumn bind:#"value" toObject:currentItemsArrayController withKeyPath:[NSString stringWithFormat:#"arrangedObjects.%#", metadata.columnBindingKeyPath] options:bindingOptions];
Examining the NSTreeController side
The NSTreeController is a 3 levels hierarchy controller, but only the first 2 levels are displayed in the outline view.
Have you confirmed that all three levels are loaded into your NSTreeController? You could do this by logging its contents to the console with the extension below. If the output produced by this code matches what appears in your outline-view, the problem is probably on the NSTreeController side, not the outline-view.
#import "NSTreeController+Logging.h"
#implementation NSTreeController (Logging)
// Make sure this is declared in the associated header file.
-(void)logWithBlock:(NSString * (^)(NSTreeNode *))block {
NSArray *topNodes = [self.arrangedObjects childNodes];
[self logNodes:topNodes withIndent:#"" usingBlock:block];
}
// For internal use only.
-(void)logNodes:(NSArray *)nodes
withIndent:(NSString *)indent
usingBlock:(NSString * (^)(NSTreeNode *))block {
for (NSTreeNode * each in nodes) {
NSLog(#"%#%#", indent, block(each));
NSString *newIndent = [NSString stringWithFormat:#" %#", indent];
[self logNodes:each.childNodes withIndent:newIndent usingBlock:block];
}
}
#end
The above code does not need to be adjusted, all you need to do is call it with a customised block:
-(void)logIt {
[self.treeController logWithBlock:^NSString *(NSTreeNode * node) {
// This will be called for every node in the tree. This implementation
// will see the object's description logged to the console - you may
// want to do something more elaborate.
NSString *description = [[node representedObject] description];
return description;
}];
}
Examining the NSOutlineView side
If all the data seems to have loaded correctly into the NSTreeController you could have a look at how your NSOutlineView is populated. The delegate method -[NSOutlineViewDelegate outlineView:viewForTableColumn:item] is a good place to start. The item argument is the NSTreeNode instance, so, before you return the relevant view you can look at this node and make sure it's behaving as expected. In your case, you should scrutinize the properties of item objects that are representing CBDisc objects. (You may need to click on disclosure buttons to get this method to fire for the relevant objects.)
-(NSView *)outlineView:(NSOutlineView *)outlineView
viewForTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
NSTreeNode *node = (NSTreeNode *)item;
NSManagedObject *representedObject = (NSManagedObject *)node.representedObject;
// Query the node
NSLog(#"%# <%#>", representedObject.description, [representedObject class]);
NSLog(#" node is a leafNode: %#", node.isLeaf ? #"YES" : #"NO");
NSLog(#" node has child-count of: %lu", (unsigned long)node.childNodes.count);
// Query the NSManagedObject
// your stuff here...
// This is app-specific - you'll probably need to change the identifier.
return [outlineView makeViewWithIdentifier:#"StandardTableCellView" owner:self];
}
So, I've figured out why.
Quite stupid: the main outline column containing the disclosure arrows was 20px width only and the arrows of the children had indentation.
I'm using the outline column for the arrows only and not the titles of the nodes, that's why it's so narrow.
I had disabled indentation and now I can see all arrows.

Cocoa Bindings - NSTableView - Swapping Values

Is an NSValueTransform subclass a good choice for displaying Core Data attributes into UI views displaying:
A number string like (0,1,2,3,etc) into a string such as (Pending, Completed, Frozen, In progress, etc)
A number string like (0,1) into a app-based image (red.png if 0, green.png if 1)
Here's what Core Data displays for the two attributes, timer and status:
Here is what I want to be displayed instead, without changing the values in Core Data:
If not to use NSValueTransformer, in what other way is this possible?
I do not want to see the data permanently converted, only for the benefit of less data stored in Core Data and better UI view items.
I have also tried to modify the attributes in the managed object class (with out KVO notification) with no luck.
Yes, NSValueTransformer subclasses work just fine for this purpose.
You can also add read-only computed properties to your managed object class, and that should work, too. Those properties can even be added by a category in the controller code, if they don't make sense as part of the model code.
For example:
+ (NSSet*) keyPathsForValuesAffectingStatusDisplayName
{
return [NSSet setWithObject:#"status"];
}
- (NSString*) statusDisplayName
{
NSString* status = self.status;
if ([status isEqualToString:#"0"])
return #"Pending";
else if ([status isEqualToString:#"1"])
return #"Completed";
// ...
}
The +keyPathsForValuesAffectingStatusDisplayName method lets KVO and bindings know that when status changes, so does this new statusDisplayName property. See the docs for +keyPathsForValuesAffectingValueForKey: to learn how that works.
I ended up using what at first appeared to be blocking the display of different info in those cells, using:
#pragma mark - Table View Delegate
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
/* tableColumn = (string) #"AutomaticTableColumnIdentifier.0"
row = (int) 0 */
NSString *identifier = [tableColumn identifier];
NSTableCellView *cellView = [tableView makeViewWithIdentifier:identifier owner:self];
NSManagedObject *item = [self.itemArrayController.arrangedObjects objectAtIndex:row];
if ([identifier isEqualToString:#"AutomaticTableColumnIdentifier.0"]) {
/* subviews returns array with 0 = Image View &
1 = Text Field */
/* First, set the correct timer image */
... logic ...
NSImageView *theImage = (NSImageView *)[[cellView subviews] objectAtIndex:0];
theImage.image = [NSImage imageNamed:#"green.gif"];
/* Second, display the desired status */
NSTextField *theTextField = (NSTextField *)[[result subviews] objectAtIndex:1];
... logic ...
theTextField.stringValue = #"Pending";
}
return cellView;
}
Apple's documentation states (somewhere) that bindings with an Array Controller can work in combination with manually populating the table view cells. It seems best and easiest to start with bindings and then refine display values manually.

Subclassing NSPopUpButton to add a bindable property

I'm trying to add a bindable property to a custom NSPopUpButton subclass.
I've created a "selectedKey" property, which is meant to store a NSString associated with selected menu item.
In control init, I set self as button target and an action for the button (valueChanged:), which in turn sets "selectedKey" in accordance with user selection:
#interface MyPopUpButton : NSPopUpButton {
NSMutableDictionary *_items;
NSString *_selectedKey;
}
#property(nonatomic, readwrite, copy) NSString* selectedKey;
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key;
#end
#implementation MyPopUpButton
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
_items = [NSMutableDictionary new];
[NSObject exposeBinding:#"selectedKey"];
[super setTarget:self];
[super setAction:#selector(valueChanged:)];
}
return self;
}
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key {
[super addItemWithTitle:title];
[_items setValue:title forKey:key];
}
- (void)valueChanged:(id)sender {
for (NSString *aKey in [_items allKeys]) {
if ([[_items valueForKey:aKey] isEqualToString:[self titleOfSelectedItem]]) {
self.selectedKey = aKey;
}
}
}
- (void)setSelectedKey:(NSString *)selectedKey {
[self willChangeValueForKey:#"selectedKey"];
_selectedKey = selectedKey;
[self didChangeValueForKey:#"selectedKey"];
[self selectItemWithTitle:[_items valueForKey:selectedKey]];
}
#end
This seems to work as expected: "selectedKey" property is changed when user changes PopUpButton selection.
Unfortunately, trying to bind this property, doesn't work.
[selectButton bind:#"selectedKey" toObject:savingDictionary withKeyPath:key options:#{NSContinuouslyUpdatesValueBindingOption : #YES }]
When selection is changed bind object is not updated accordingly.
What am I doing wrong?
I've created a "selectedKey" property, which is meant to store an NSString associated with selected menu item.
Bindings is definitely the way to go here, but your use of bind:toObject:withKeyPath:options is incorrect.
The value that you pass to the first argument must be one of the predefined values made available by Apple for that particular control. For NSPopUpButton objects, the available values are documented in the NSPopUpButton Bindings Reference. When you look through this document you'll see that there is no selectedKey option. There is however a selectedValue which has the following description:
An NSString that specifies the title of the selected item in the NSPopUpButton.
Thus the correct way to set up the binding is as follows:
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
This is all you need to do: when the action selector is fired the property stored at the keyPath you passed in as the third argument will already have been updated. This means that you can (i) get rid of the setSelectedKey method entirely, (ii) remove exposeBinding line, and (iii) remove the code within valueChanged: - Cocoa has already done this bit.
The example below implements just two methods, but, if I've understood your intentions, they should be all you need:
- (void)awakeFromNib {
self.btn.target = self;
self.btn.action = #selector(popUpActivity:);
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
// I've added a couple of additional bindings here; they're
// not required, but I thought they'd be instructive.
[self.btn bind:#"content"
toObject:self
withKeyPath:#"myItems"
options:nil];
[self.btn bind:#"selectedIndex"
toObject:self
withKeyPath:#"mySelectedIndex"
options:nil];
// Now that you've set the bindings up, use them!
self.myItems = #[#"Snow", #"Falling", #"On", #"Cedars"];
self.mySelectedIndex = #3; // "Cedars" will be selected on startup
// no need to set value of mySelectedString, because it will be
// updated automatically by the selectedIndex binding.
NSLog("%#", self.mySelectedString) // -> "Cedars"
}
- (void)popUpActivity:(id)sender {
NSLog(#"value of <selectedIndex> -> %#", self.mySelectedIndex);
NSLog(#"value of <selectedString> -> %#", self.mySelectedString);
}
A final point worth making is that none of the above should be a part of an NSPopUpButton subclass. It looks like you can - and therefore should - do everything you need to do without a custom subclass of this control. In my demo-app the code above belongs to the ViewController class, you should try doing this also.

How to name Undo menu entries for Core Data add/remove items via bindings and NSArrayController?

I have a NSTableView populated by a Core Data entity and Add Item / Remove Item buttons all wired with a NSArrayController and bindings in Interface Builder.
The Undo/Redo menu items can undo or redo the add / remove item actions.
But the menu entries are called only „Undo“ resp. „Redo“.
How can i name them like „Undo Add Item“, „Undo Remove Item“, etc.
(I am aware, something similar was asked before, but the accepted answers are either a single, now rotten link or the advice to subclass NSManagedObject and override a method that Apples documentation says about: "Important: You must not override this method.“)
Add a subclass of NSArrayController as a file in your project. In the xib, in the Identity Inspector of the array controller, change the Class from NSArrayController to your new subclass.
Override the - newObject method.
- (id)newObject
{
id newObj = [super newObject];
NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
[undoManager setActionName:#"Add Item"];
return newObj;
}
Also the - remove:sender method.
- (void)remove:(id)sender
{
[super remove:sender];
NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
[undoManager setActionName:#"Remove Item"];
}
Register for NSManagedObjectContextObjectsDidChangeNotification:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(mocDidChangeNotification:)
name:NSManagedObjectContextObjectsDidChangeNotification
object: nil];
And parse the userInfo dictionary in the corresponding method:
- (void)mocDidChangeNotification:(NSNotification *)notification
{
NSManagedObjectContext* savedContext = [notification object];
// Ignore change notifications for anything but the mainQueue MOC
if (savedContext != self.managedObjectContext) {
return;
}
// Ignore updates -- lots of noise from maintaining user-irrelevant data
// Set actionName for insertion
for (NSManagedObject* insertedObject in
[notification.userInfo valueForKeyPath:NSInsertedObjectsKey])
{
NSString* objectClass = NSStringFromClass([insertedObject class]);
savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ?
[NSString stringWithFormat:#"Delete %#", objectClass] :
[NSString stringWithFormat:#"Insert %#", objectClass];
}
// Set actionName for deletion
for (NSManagedObject* deletedObject in
[notification.userInfo valueForKeyPath:NSDeletedObjectsKey])
{
NSString* objectClass = NSStringFromClass([deletedObject class]);
savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ?
[NSString stringWithFormat:#"Insert %#", objectClass] :
[NSString stringWithFormat:#"Delete %#", objectClass];
}
}
I've tested this in my own code-- it's rough. Can spend a lot more time making the actionName nicer. I deleted parsing of updates because: 1) insertions and deletions of objects in to-many relationships generate updates of other objects 2) I don't care to figure out how to discover what properties changed at this time
I also have class names that aren't user-friendly, so this is a great time to implement the description function for all entities, and use that rather than the class name.
But this at least works for all object controllers in a project, and easily enough for insert and delete.
[edit] Updated with mikeD's suggestion to cover redo having an inverse name. Thanks!

NSTreeController/NSOutlineView loses its selection

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
By my reading of the docs and headers, NSTreeController uses NSIndexPaths to store selection. This means that its idea of selection is a chain of indexes into a tree of nested arrays. So as far as it knows, it is preserving the selection in the situation you describe. The problem here is the you're thinking of selection in terms of "object identity" and the tree controller defines selection as "a bunch of indexes into nested array". The behavior you describe is (AFAICT) the expected out-of-the-box behavior for NSTreeController.
If you want selection preservation by object identity, my suggestion would be to subclass NSTreeController and override all mutating methods such that you capture the current selection using -selectedNodes before the mutation, then re-set the selection using -setSelectionIndexPaths: with an array created by asking each formerly selected node for its new -indexPath after the mutation.
In short, if you want behavior other than the stock behavior, you're going to have to write it yourself. I was curious how hard this would be so I took a stab at something that appears to work for the cases I bothered to test. Here 'tis:
#interface SOObjectIdentitySelectionTreeController : NSTreeController
#end
#implementation SOObjectIdentitySelectionTreeController
{
NSArray* mTempSelection;
}
- (void)dealloc
{
[mTempSelection release];
[super dealloc];
}
- (void)p_saveSelection
{
[mTempSelection release];
mTempSelection = [self.selectedNodes copy];
}
- (void)p_restoreSelection
{
NSMutableArray* array = [NSMutableArray array];
for (NSTreeNode* node in mTempSelection)
{
if (node.indexPath.length)
{
[array addObject: node.indexPath];
}
}
[self setSelectionIndexPaths: array];
}
- (void)insertObject:(id)object atArrangedObjectIndexPath:(NSIndexPath *)indexPath
{
[self p_saveSelection];
[super insertObject: object atArrangedObjectIndexPath: indexPath];
[self p_restoreSelection];
}
- (void)insertObjects:(NSArray *)objects atArrangedObjectIndexPaths:(NSArray *)indexPaths
{
[self p_saveSelection];
[super insertObjects:objects atArrangedObjectIndexPaths:indexPaths];
[self p_restoreSelection];
}
- (void)removeObjectAtArrangedObjectIndexPath:(NSIndexPath *)indexPath
{
[self p_saveSelection];
[super removeObjectAtArrangedObjectIndexPath:indexPath];
[self p_restoreSelection];
}
- (void)removeObjectsAtArrangedObjectIndexPaths:(NSArray *)indexPaths
{
[self p_saveSelection];
[super removeObjectsAtArrangedObjectIndexPaths:indexPaths];
[self p_restoreSelection];
}
#end
EDIT: It a little brutal (performance-wise) but I was able to get something working for calls to -setContent: as well. Hope this helps:
- (NSTreeNode*)nodeOfObject: (id)object
{
NSMutableArray* stack = [NSMutableArray arrayWithObject: _rootNode];
while (stack.count)
{
NSTreeNode* node = stack.lastObject;
[stack removeLastObject];
if (node.representedObject == object)
return node;
[stack addObjectsFromArray: node.childNodes];
}
return nil;
}
- (void)setContent:(id)content
{
NSArray* selectedObjects = [[self.selectedObjects copy] autorelease];
[super setContent: content];
NSMutableArray* array = [NSMutableArray array];
for (id object in selectedObjects)
{
NSTreeNode* node = [self nodeOfObject: object];
if (node.indexPath.length)
{
[array addObject: node.indexPath];
}
}
[self setSelectionIndexPaths: array];
}
Of course, this relies on the objects actually being identical. I'm not sure what the guarantees are with respect to CoreData across your (unknown to me) background operation.