data-bindingnstableviewkey-value-observingnsarraycontroller

Identify modified row for NSTableView bound with NSArrayController


I have an NSTableView bound to an NSArrayController, and have registered an observer for whenever data changes to the data fields that the array controller manages. For example, here's a few of the addObserver calls:

    [_arrayController addObserver:self forKeyPath:@"arrangedObjects.userName"     options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    [_arrayController addObserver:self forKeyPath:@"arrangedObjects.enabled"      options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    [_arrayController addObserver:self forKeyPath:@"arrangedObjects.lockSettings" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

After that, whenever data is changed by editing the table, my observer gets called just fine.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if ([keyPath compare:@"arrangedObjects.userName"] == NSOrderedSame)
    {
        // Great, we know a userName changed somewhere in the table, but which one?
    }
}

The problem is, I need to know which one of the arranged objects triggered this. If I knew the tableview row that would work as well. It's nice to know that some userName changed somewhere, but I really need to know which one of the potentially hundreds caused the observer to be called.

Sorry for the old Objective C format of this, but some of us still have tons of legacy code to deal with.


Solution

  • View-based table view

    Connect the text fields in the cells to an action method of the delegate of the table view. Get the row with NSTableView method rowForView: and the column with columnForView:.

    - (IBAction)editAction:(id)sender {
        NSInteger row = [self.tableView rowForView:sender];
        NSInteger column = [self.tableView columnForView:sender];
        NSLog(@"Edited row: %ld, column: %ld", (long)row, (long)column);
        …
    }
    

    Cell-based table view

    Implement NSControlTextEditingDelegate method controlTextDidEndEditing: on the delegate of the table view. The edited row and column are editedRow and editedColumn of the table view.

    - (void)controlTextDidEndEditing:(NSNotification *)aNotification {
        NSInteger row = self.tableView.editedRow;
        NSInteger column = self.tableView.editedColumn;
        NSLog(@"Edited row: %ld, column: %ld", (long)row, (long)column);
        …
    }
    

    Key

    There are several ways to get the key:

    NSString *key = [[tableColumnOrTextField infoForBinding:NSValueBinding] objectForKey:NSObservedKeyPathKey]; // something like "arrangedObjects.name"
    NSRange aRange = [key rangeOfString:@"."];
    if (aRange.location != NSNotFound)
        key = [key substringFromIndex:aRange.location + 1];
    

    Bindings solution (no coding required)

    Edit the values in a detail view. Bind the text fields and other controls to controller key selection of the array controller. "Allowes Editing Multiple Values Selection" is switched on by default. Works for cell-based and view-based table views.