Search code examples
objective-cmacoscocoanstableviewnsarraycontroller

NSTableView sort bindings - programatic Solution


First-time questioner here on the site. I have been attempting to learn more about NSTableView bindings with relation to an NSArrayController by taking the bindings out of the storyboard and trying to reproduce the effect in code. I am able to configure the initial sort order of the NSTableView and NSArrayController using bindings but I still cannot seem to get the columns to sort when they are clicked.

Background is that I have a standard, cell-based NSTableView in a storyboard that is bound to a NSViewController through an IBOutlet. The NSViewController has the following viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];

    //setup the array controller
    self.arrayController = [NSArrayController new];
    [self.arrayController addObject:@{@"namef": @"Test1", @"namel": @"a"}];
    [self.arrayController addObject:@{@"namef": @"Test2", @"namel": @"b"}];
    [self.arrayController addObject:@{@"namef": @"Test3", @"namel": @"c"}];
    [self.arrayController addObject:@{@"namef": @"Test4", @"namel": @"B"}];

    //setup the table view column bindings and sorting
    [[self.tableView tableColumnWithIdentifier:@"0"] bind:NSValueBinding toObject:self.arrayController withKeyPath:@"arrangedObjects.namef" options:nil];
    [[self.tableView tableColumnWithIdentifier:@"0"] setSortDescriptorPrototype:[NSSortDescriptor sortDescriptorWithKey:@"namef" ascending:YES selector:@selector(caseInsensitiveCompare:)]];

    [[self.tableView tableColumnWithIdentifier:@"1"] bind:NSValueBinding toObject:self.arrayController withKeyPath:@"arrangedObjects.namel" options:nil];
    [[self.tableView tableColumnWithIdentifier:@"1"] setSortDescriptorPrototype:[NSSortDescriptor sortDescriptorWithKey:@"namel" ascending:YES selector:@selector(caseInsensitiveCompare:)]];

    //Bind the array controller to the tableView
    [self.tableView bind:NSContentBinding toObject:self.arrayController withKeyPath:@"arrangedObjects" options:nil];
    [self.tableView bind:NSSelectionIndexesBinding toObject:self.arrayController withKeyPath:@"selectionIndexes" options:nil];

    //setup sorting
    [self.tableView setSortDescriptors:[NSArray arrayWithObject: [[NSSortDescriptor alloc] initWithKey:@"namel" ascending:YES selector:@selector(caseInsensitiveCompare:)]]];
    [self.arrayController bind:NSSortDescriptorsBinding toObject:self withKeyPath:@"sortDescriptors" options:nil];
    [self.arrayController setAutomaticallyRearrangesObjects:YES];
}

This results in a successful loaded screen with the table view sorted by the "namel" data AND each column header is showing as clickable with arrows aiming up/down with each click.

However, the sort order is not changing...

Reading various other articles and stackoverflow questions I am seeing answers like "you just need to bind the columns to the array controller and the NSTableView will auto-bind itself" and various other explanations that regularly involve the storyboard.

I've tried various combinations of commenting out components of the below code. I've tried changing the text inside of the sortDescriptorPrototypes in each column.

I know I'm missing one important clue and the documentation on this is horrible. Can anyone see what I'm doing wrong? How do I get this to bind correctly so that clicking on the column headers actually sorts the data?


Solution

  • So as is common with these things I found the answer myself. So to those who will come after, read below:

    My problem was that I had connected too many bindings between the NSArrayController and the NSTableView. The bindings in my question were :

    a) 2 table-view columns binding their values to the contents of the array-controller

    b) the table-view also binding it's contents to the array-controller

    c) the table-view binding it's selection index to the array-controller

    d) the array controller binding it's sort descriptors to a local reference of the table-view sort descriptors.

    I removed bindings b and c and everything started working. I thought I had tried that before but there must have been an additional bug when I tried it then. Below is the functional copy of my viewDidLoad method:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        //setup the array controller
        self.arrayController = [NSArrayController new];
    
        [self.arrayController addObject:@{@"namef": @"Test1", @"namel": @"a"}];
        [self.arrayController addObject:@{@"namef": @"Test2", @"namel": @"b"}];
        [self.arrayController addObject:@{@"namef": @"Test3", @"namel": @"c"}];
        [self.arrayController addObject:@{@"namef": @"Test4", @"namel": @"B"}];
    
        //setup the table view
        [[self.tableView tableColumnWithIdentifier:@"0"] bind:NSValueBinding toObject:self.arrayController withKeyPath:@"arrangedObjects.namef" options:nil];
        [[self.tableView tableColumnWithIdentifier:@"0"] setSortDescriptorPrototype:[NSSortDescriptor sortDescriptorWithKey:@"namef" ascending:YES selector:@selector(caseInsensitiveCompare:)]];
    
        [[self.tableView tableColumnWithIdentifier:@"1"] bind:NSValueBinding toObject:self.arrayController withKeyPath:@"arrangedObjects.namel" options:nil];
        [[self.tableView tableColumnWithIdentifier:@"1"] setSortDescriptorPrototype:[NSSortDescriptor sortDescriptorWithKey:@"namel" ascending:YES selector:@selector(caseInsensitiveCompare:)]];
    
        //setup sorting
        [self.tableView setSortDescriptors:[NSArray arrayWithObject: [[NSSortDescriptor alloc] initWithKey:@"namel" ascending:YES selector:@selector(caseInsensitiveCompare:)]]];
        [self.arrayController bind:NSSortDescriptorsBinding toObject:self.tableView withKeyPath:@"sortDescriptors" options:nil];
        [self.arrayController setAutomaticallyRearrangesObjects:YES];
    
    }