views:

365

answers:

2

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];
}
A: 

I would probably approach this in a different way, by implementing the outlineView:willDisplayCell:forTableColumn:item: delegate method, and setting the isChecked property of the cell there, rather than from within the cell subclass. You would then just bind that column directly to arrangedObjects.title, so the default editing mechanism would take care of setting the property on the managed object instance.

IIRC, the item parameter you get passed will actually be an NSTreeNode instance whose representedObject property will give you the NSManagedObject instance for that row, so you can get whatever info you need from it that way.

Brian Webster
outlineView:willDisplayCell:forTableColumn:item works for reading the value, but what about updating the isChecked value in the table cell as well? In the past I've just setup delegate methods in my NSCell subclass to handle the changes after something clicked and implemented them in my controller. It just seems I could/should do this in a cleaner way using KVC/KVO. Maybe I'm wrong?
Justin Williams
+3  A: 

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.

Chris Hanson