Search code examples
cocoacore-datacocoa-bindingskey-value-coding

Update bound dictionary based on NSTextFieldCell's edited value


I'm working on porting some ancient code (10.2 era) from NSCoding/plist based archiving to using Core Data. I have an NSOutlineView with a custom NSTextFieldCell. The outline view is bound to an NSTreeController to provide the data.

The bindings model looks like this:

NSTreeController: Managed Object Context -> Controller.managedObjectContext

NSOutlineView's NSTableColumn Value -> Tree Controller:arrangedObjects:itemDictionary

The NSOutlineView has a custom NSTextFieldCell subclass that adds an image next to the text field, so I am passing the NSManagedObject's values to it as an NSMutableDictionary called itemDictionary so I can pull and set the title and isChecked key values.

Where I am running into issues is updating the text field's value and passing that changed value back to my managed object instance. After the user double-clicks on the title value and edits it, it is passed to -(id)objectValue, but I'm not sure what the next step is to get the update propagated to my NSManagedObject instance. The code I have thus far for reading and setting values in my NSTextFieldCell subclass is below:

- (void)setStringValue:(NSString *)aString {
  [super setObjectValue:aString];
}

- (void)setObjectValue:(id <NSCopying>)anObject {  
  id cellValues = anObject;

  [super setObjectValue:[cellValues valueForKey:@"title"]];
  [self setCheckState:[[cellValues valueForKey:@"isChecked"] integerValue]];
}

- (id)objectValue {
  return [super objectValue];
}

Solution

  • I asked around, and this is the recommendation someone gave me; it looks reasonable.

    In your NSCell subclass, in whatever method is invoked by the event loop upon setting a new value, do something like this:

    - (void)whateverMethodInCellSubclassIsTriggeredByEventLoop:(id)value {
        NSTableView *tableView = [self controlView];
        NSTableColumn *column = [[tableView tableColumns] objectAtIndex:[tableView editedColumn]];
        NSInteger rowIndex = [tableView editedRow];
        NSDictionary *bindingInfo = [column infoForBinding:NSValueBinding];
        id modelObject = nil;
    
        if ([controlView isKindOfClass:[NSOutlineView class]]) {
            NSTreeNode *item = [outlineView itemAtRow:rowIndex];
            modelObject = [item representedObject];
        } else if ([controlView isKindOfClass:[NSTableView class]]) {
            NSArrayController *controller = [bindingInfo objectForKey:NSObservedObjectKey];
            modelObject = [[controller arrangedObjects] objectAtIndex:rowIndex];
        }
    
        [modelObject setValue:value forKeyPath:[bindingInfo objectForKey:NSObservedKeyPathKey]];
    }
    

    This is fairly generic code that leverages the binding info available on the table column to get the model object and key path to which your changes should be pushed, and to use generic KVC to push the changes. It should work for both table and outline views as well as for arbitrary model objects, Core Data or not.