Search code examples
cocoacore-dataselectioncocoa-bindingsnsoutlineview

How to automatically select an item inserted into and NSOutlineView & Core Data


I have an NSTreeController bound an NSArrayController bound to an entity and the tree bound to an NSOutlineView. Now, when an "add" button is clicked, I would like to add a new entity to the treeController, select the entity, and highlight it for editing. If I call [arrayController add], the insertion is asynchronous and I have no way of knowing which the new object is since the outline view does not select new rows automatically. So I am left with inserting the new Entity programatically. So addButton calls createNewGroup on a outlineViewController (see below).

Again, inserting a new entity does not seem to be a synchronous process. I can't locate it in the NSOutlineView in the next line after currentObject = [NSEntityDescription.... And I did try after reloading the data. So I am left with observing the value changes in the array controller. This sort of works, most of the time, but occasionally it does not. Is this the right approach to this sort of thing?

- (void) createNewGroup:(id)sender {
    NSInteger row = [myOutlineView selectedRow];
    if(row == -1) {
        [groupsController addObserver:self 
                           forKeyPath:IR_GROUPS_KEYPATH 
                              options:NSKeyValueObservingOptionInitial 
                              context:IR_GROUPS_CONTEXT];
        currentObject = [NSEntityDescription insertNewObjectForEntityForName:@"Group" 
                                                      inManagedObjectContext:appDelegate.managedObjectContext];
        return;
    }
    if([myOutlineView levelForRow:row] != 0) return;
    [subGroupsController addObserver:self 
                          forKeyPath:IR_GROUPS_KEYPATH 
                             options:NSKeyValueObservingOptionInitial 
                             context:IR_SUBGROUPS_CONTEXT];
    NSManagedObject *parent = [[myOutlineView itemAtRow:row] representedObject];
    currentObject = [NSEntityDescription insertNewObjectForEntityForName:@"Group" 
                                                            inManagedObjectContext:appDelegate.managedObjectContext];
    [currentObject setValue:parent forKey:@"parent"];
}

- (void) observeValueForKeyPath:(NSString *)keyPath 
                       ofObject:(id)object 
                         change:(NSDictionary *)change 
                        context:(void *)context {

    if([keyPath isEqualToString:IR_GROUPS_KEYPATH]) {
        if(currentObject == nil) return;
        [myOutlineView noteNumberOfRowsChanged];
        NSString *ctx = (NSString *) context;
        if([ctx isEqualToString:IR_GROUPS_CONTEXT]) {

            NSInteger length = [myOutlineView numberOfRows];
            NSInteger index;
            for(index = 0; index < length; index++) {
                id item = [myOutlineView itemAtRow:index];
                if(currentObject == [item representedObject]) {
                    // We found the new object:
                    NSIndexSet *indices = [NSIndexSet indexSetWithIndex:index];
                    [myOutlineView selectRowIndexes:indices byExtendingSelection:NO];
                    [myOutlineView editColumn:0 row:index withEvent:nil select:YES];
                    currentObject = nil;
                    return;
                }
            }
            //[groupsController removeObserver:self forKeyPath:nil];

        } else if([ctx isEqualToString:IR_SUBGROUPS_CONTEXT]) {
            NSTreeNode *parent = [myOutlineView itemAtRow:[myOutlineView selectedRow]];
            [myOutlineView expandItem:parent];
            NSInteger length = [myOutlineView numberOfRows];
            NSInteger index;
            for(index = 0; index < length; index++) {
                id item = [myOutlineView itemAtRow:index];
                if(currentObject == [item representedObject]) {
                    NSIndexSet *indices = [NSIndexSet indexSetWithIndex:index];
                    [myOutlineView selectRowIndexes:indices byExtendingSelection:NO];
                    [myOutlineView editColumn:0 row:index withEvent:nil select:YES];
                    currentObject = nil;
                    return;
                }
            }
        }
    }
}

Solution

  • If you use (a subclass of) NSTreeController to provide the content for you outlineView, it is very simple. You create a button, either in code or in Interface Builder, and set bind the target to insert: to add an element or remove: to delete it. In code it would look like this:

    [aButton bind:NSTargetBinding 
         toObject:aController 
      withKeyPath:keyPathToTreeController
        options:[NSMutableDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSConditionallySetsEnabledBindingOption,
                                 @"insert:", NSSelectorNameBindingOption,
                                 nil]];
    

    Selecting the new object is handled by the treeController. Again, in code:

    [aTreeController setSelectsInsertedObjects:YES];
    

    In IB, it's a checkbox that you need to check. Oh, there's also addChild:. Let the bindings do their magic.