Question

I am struggling trying to perform what I would think would be a relatively common task. I have an NSTableView that is bound to it's array via an NSArrayController. The array controller has it's content set to an NSMutableArray that contains one or more NSObject instances of a model class. What I don't know how to do is expose the model inside the NSCell subclass in a way that is bindings friendly.

For the purpose of illustration, we'll say that the object model is a person consisting of a first name, last name, age and gender. Thus the model would appear something like this:

@interface PersonModel : NSObject {
    NSString * firstName;
    NSString * lastName;
    NSString * gender;
    int * age;
}

Obviously the appropriate setters, getters init etc for the class.

In my controller class I define an NSTableView, NSMutableArray and an NSArrayController:

@interface ControllerClass : NSObject {
    IBOutlet NSTableView * myTableView;
    NSMutableArray * myPersonArray;
    IBOutlet NSArrayController * myPersonArrayController;
}

Using Interface Builder I can easily bind the model to the appropriate columns:

myPersonArray --> myPersonArrayController --> table column binding

This works fine. So I remove the extra columns, leaving one column hidden that is bound to the NSArrayController (this creates and keeps the association between each row and the NSArrayController) so that I am down to one visible column in my NSTableView and one hidden column. I create an NSCell subclass and put the appropriate drawing method to create the cell. In my awakeFromNib I establish the custom NSCell subclass:

MyCustomCell * aCustomCell = [[[MyCustomCell alloc] init] autorelease];
[[myTableView tableColumnWithIdentifier:@"customCellColumn"] 
     setDataCell:aCustomCell];

This, too, works fine from a drawing perspective. I get my custom cell showing up in the column and it repeats for every managed object in my array controller. If I add an object or remove an object from the array controller the table updates accordingly.

However... I was under the impression that my PersonModel object would be available from within my NSCell subclass. But I don't know how to get to it. I don't want to set each NSCell using setters and getters because then I'm breaking the whole model concept by storing data in the NSCell instead of referencing it from the array controller.

And yes I do need to have a custom NSCell, so having multiple columns is not an option. Where to from here?

In addition to the Google and StackOverflow search, I've done the obligatory walk through on Apple's docs and don't seem to have found the answer. I have found a lot of references that beat around the bush but nothing involving an NSArrayController. The controller makes life very easy when binding to other elements of the model entity (such as a master/detail scenario). I have also found a lot of references (although no answers) when using Core Data, but Im not using Core Data.

As per the norm, I'm very grateful for any assistance that can be offered!

Was it helpful?

Solution

Finally figured this one out. Man that took some doing. So here is what I needed to do...

First of all I needed to create an array of my model's key values in my model object and return those key values in an NSDictionary from within the model.

Thus my model got two new methods as follows (based on my simplified example above):

+(NSArray *)personKeys
{
    static NSArray * personKeys = nil;
    if (personKeys == nil)
        personKeys = [[NSArray alloc] initWithObjects:@"firstName", @"lastName", @"gender", @"age", nil];

    return personKeys;
}

-(NSDictionary *)personDictionary
{
    return [self dictionaryWithValuesForKeys:[[self class] personKeys]];
}

Once implemented, I assign my bound value in my table to

arrayController --> arrangeObjects --> personDictionary.

The last step is to reference the object in the NSCell drawWithFrame and use as needed as follows:

NSDictionary * thisCellObject = [self objectValue];

NSString * objectFirstName = [thisCellObject valueForkey:@"firstName"];
NSString * objectLastName = [thisCellObject valueForKey:@"lastName"];

And as I hoped, any update to the model object reflects in the custom NSCell.

OTHER TIPS

There is another approach that does not require the dictionary. However, it does require the implementation of the - (id)copyWithZone:(NSZone *)zone method within your data class. For example:

- (id)copyWithZone:(NSZone *)zone {
    Activity *copy = [[self class] allocWithZone: zone];
    copy.activityDate = self.activityDate;
    copy.sport = self.sport;
    copy.sportIcon = self.sportIcon;
    copy.laps = self.laps;
    return copy; }

Now in IB, you point the Table Column's value at Array Controller --> arrangedObjects

The drawWithFrame method will now return your actual object from the objectValue.

Activity *rowActivity = [self objectValue];

Changing the class requires updating the copyWithZone method and then accessing the data directly in your drawWithFrame method.

I'm very grateful for this post, Hooligancat, because my project was stalled on this problem, and as hard as I looked at Tim Isted's identical solution at http://www.timisted.net/blog/archive/custom-cells-and-core-data/ I couldn't figure it out.

It was only when I read your excellent simplification of the problem and your version of the solution that the penny dropped - I'm very grateful!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top