Populating a NSTableView or NSCollectionView using Cocoa Bindings without XIBs

| | August 4, 2015

I would like to bind my NSTableView or NSCollectionView to a datasource consisting of a NSArrayController or NSArray property. However, I wish to do this in code, as I have no XIBs in my UI for various technical reasons.

As Apple does not provide any documentation on how to do so, I’m looking for a canonical answer, giving a good representation on how this is supposed to be done.

It doesn’t matter if the answer is in Objective-C or Swift.

Here’s what doesn’t work:

    toObject: myViewModel,
    withKeyPath: "contacts",
    options: nil)

With “contacts” being either a NSArrayController or NSArray with strings in.

One Response to “Populating a NSTableView or NSCollectionView using Cocoa Bindings without XIBs”

  1. You need to bind to an NSArrayController, not just an NSArray because you almost certainly want to bind through the array elements to their properties and NSArray does not support that. Bindings is built on top of Key-Value Observing and you can’t key-value observe through arrays or sets.

    Also, an array controller is usually part of the controller layer in Model-View-Controller. So, it doesn’t make sense that “contacts” could be a key path naming a property of myViewModel and also be an array controller. It should be a property of the view or window controller.

    If your table view is NSCell-based, you should usually bind the table columns rather than the table view itself. The table view will automatically bind a few of its own bindings based on the bindings of its columns. You would only explicitly bind the table view’s bindings if you want to disable that automatic behavior.

    So, you might do, for each column:

    [tableColumn bind:NSValueBinding toObject:self.contactsArrayController withKeyPath:@"arrangedObjects.propertyAppropriateToColumn" options:nil];

    If your table view is view-based, you should bind the table view’s bindings and not the columns. So, you would do:

    [contactsTableView bind:NSContentBinding toObject:self.contactsArrayController withKeyPath:@"arrangedObjects" options:nil];
    [contactsTableView bind:NSSelectionIndexesBinding toObject:self.contactsArrayController withKeyPath:@"selectionIndexes" options:nil];
    [contactsTableView bind:NSSortDescriptorsBinding toObject:self.contactsArrayController withKeyPath:@"sortDescriptors" options:nil];

    If your table cell view is NSTableCellView or a subclass, then the table view will set its objectValue property to the corresponding element from the array. The views within the NSTableCellView should bind to it, using a key path like objectValue.propertyAppropriateToThatView. If you’re not using NIBs, you’re going to have to set up this binding in your -tableView:viewForTableColumn:row: delegate method. You also have to tear down the bindings, which is going to be kind of hard because the table view doesn’t tell you when it discards a cell view. It might work to do the setup in -tableView:didAddRowView:forRow: and teardown in -tableView:didRemoveRowView:forRow:, but those don’t directly give you the cell views. You’ll need to correlate the cell views within the row view to the table columns yourself. (Not hard, just tedious.)

    If your table cell view is a control or other view which responds to -setObjectValue:, then the table view will call that to set the object value to the element of the array. In that case, you may want to change the content binding to go through arrangedObjects to the relevant property of array elements.

    The situation with a collection view is similar to a view-based table view. You’d only bind NSContentBinding and NSSelectionIndexesBinding; there’s no NSSortDescriptorsBinding because collection view’s don’t have an interface for the user to change the sorting (like table column headers). The collection view will set each collection view item’s representedObject to the element of the array. The item view or its subviews should bind to the item, through representedObject to some specific property of the element.

    If you aren’t using NIBs, you’ll probably want to use a custom subclass of NSCollectionViewItem. That will set up the bindings in its -viewDidLoad method (10.10 or later) or its -loadView method after calling through to super. You can tear down the bindings when it deallocates. Alternatively, you can set them up and tear them down in -viewWillAppear and -viewWillDisappear.

Leave a Reply