Programmatically adding columns (with bindings) to a view-based NSTableView?

4.6k views Asked by At

I've got a simple app that lists some models in a NSTableView.

The table is a completely standard view-based table with a couple of columns, and is populated through an NSArrayController using bindings. All of that is set up in Interface Builder, and it works just as expected.

Now, I'd like to add some more columns programmatically, and have these new columns bind to various keypaths in the models. And there, I'm stuck.

For simplicity's sake, assume this setup:

  1. The Model objects are plain objects with just a single NSString property: name
  2. The AppDelegate has an NSArray property (models) which holds a number of Model instances.
  3. The NSArrayController's content is bound to the application delegate's models array.
  4. The NSTableView's content is bound to the array controller's arrangedObjects.
  5. The table's first column is bound (in IB) to display the name property, i.e. column → table cell view → static text table view cell → value is bound to the table cell view's objectValue.name

This is, as far as I know, straightforward and by the book, and it works like a charm.

But... how do I add more columns, then?

To add a column programatically (to keep it simple, let's just say this new column should also just display the name property, just like the column that's already there), I imagine I'd do something like this in the app delegate:

NSTableColumn* newColumn = [[NSTableColumn alloc] initWithIdentifier:@"newColumn"];

// do binding magic somehow
// [[newColumn dataCell] bind:NSValueBinding toObject:??? withKeyPath:??? options:nil];

[self.table addColumn:newColumn]; // the table's connected with an IBOutlet

I've tried so many combinations of binding from/to the data cell, the table, the column, the array controller, the array itself, as well as all manner of keypaths, but nothing works. The new column is added just fine, but it's never populated.

I imagine that the basic NSTableColumn instance I create is cell-based, not view based, and that's causing trouble. However, I have no idea how to proceed from here, and the docs never talk about binding columns that have been added programmatically.

In this case, all I want is a simple text column, bound to the model, exactly like the one I can easily set up IB.

I could do all this by implementing an NSTableViewDataSource and "manually" feeding the table, but I've got everything else set up with bindings already.

Any help would be greatly appreciated.

2

There are 2 answers

1
Hussain Shabbir On

Try like this:-

NSString *akey = @"keyValue";
NSString *keypath = [NSString stringWithFormat:@"arrangedObjects.%@",akey];
NSTableColumn *tableColumn=[[NSTableColumn alloc]initWithIdentifier:@"newColumn"];

[tableView addTableColumn:tableColumn];
[tableColumn bind:NSValueBinding toObject:yourArrayController withKeyPath:keypath options:nil];
NSMutableDictionary *dc=[NSMutableDictionary dictionary];
[dc setObject:@"textValue" forKey:@"keyValue"];
[yourmutableArray addObject:dc];
[self setYourmutableArray:yourmutableArray];

Please note:- your mutable array should bind to arraycontroller then only it will populate the data

0
Jonathan Mitchell On

You need to bind to the subviews of column's NSTableCellView using this delegate method:

- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    // Get cell view - NSTableCellView
    // This can be a prototype loaded from the table view or from another nib
    // see : - registerNib:forIdentifier:
    NSView *cellView = [tableView makeViewWithIdentifier:@"Cell1" owner:[tableView delegate]];

    // - subViewWithIdentifier is a simple category method that searches -subviews
    NSView *subView = [cellView subViewWithIdentifier:@"TextField1"];
    [subView bind: NSValueBinding toObject:cellView withKeyPath: @"objectValue.name" options: nil];

    subView = [cellView subViewWithIdentifier:@"TextField2"];
    [subView bind: NSValueBinding toObject:cellView withKeyPath: @"objectValue.address" options: nil];

    return cellView;
}

NSView category method:

- (NSView *)subViewWithIdentifier:(NSString *)theIdentifier
{
    __block NSView *subView = nil;
    [[self subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
#pragma unused(idx)
        if ([[obj identifier] isEqualToString:theIdentifier]) {
            subView = obj;
            *stop = YES;
        }
    }];
    return subView;
}