4

To support a different language than Swift and ObjC, I need to understand how to set up an NSTreeController for an NSOutlineView.

I have already been able to create the NSOutlineView in code, along with providing my own DataSource delegate. But now I like to switch to using NSTreeController. I have trouble figuring out how to set up the bindings and other relationships, as all the examples I can find assume setting this up using Interface Builder.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
  • Almost every attribute in IB has a property counterpart. Bindings can be set up in code. Which attribute or binding is the problem? – Willeke Mar 14 '17 at 16:43
  • I had never used bindings, so I am starting at zero. Was hoping someone would have a code snipped to share for this task. Now I'm almost done figuring this out – Thomas Tempelmann Mar 14 '17 at 17:01
  • Jeez - just now I found that there is already such a question with helpful example code: http://stackoverflow.com/q/31827582/43615 - I was not finding that before, oddly. I could still make this a more extensive answer or I could delete this question. – Thomas Tempelmann Mar 14 '17 at 17:09

1 Answers1

3

I think I've figured the basics out now. For the record, here's what I had to add to my view controller that already managed an NSOutlineView where I managed the datasource myself:

Create a class for the tree nodes:

@interface DataNode : NSObject {}
    @property (retain) NSMutableArray *children;
    @property (retain) NSString *firstText;   // text for 1st column
    @property (retain) NSString *secondText;  // text for 2nd column
@end

@implementation DataNode
- (instancetype)init {
    self.children = [NSMutableArray array];
    return self;
}
- (BOOL) isLeaf {
    return self.children.count == 0;
}
@end

Add these properties to your view controller:

@property (nonatomic, retain) NSTreeController *treeController;
@property (nonatomic, retain) NSMutableArray *treeContents;

Initialize the tree controller, e.g. from the view controller's awakeFromNib:

self.treeContents = [NSMutableArray array]; // holds the add nodes

self.treeController = [[NSTreeController alloc] init];
[self.treeController setLeafKeyPath:@"isLeaf"]; // refers to DataNode
[self.treeController setChildrenKeyPath:@"children"]; // refers to DataNode

// set up the bindings
[self.treeController bind:@"contentArray" toObject:self withKeyPath:@"treeContents" options:@{NSRaisesForNotApplicableKeysBindingOption:@YES, NSConditionallySetsEditableBindingOption:@YES}];
[self.table bind:@"content" toObject:self.treeController withKeyPath:@"arrangedObjects" options:@{NSAlwaysPresentsApplicationModalAlertsBindingOption:@YES}];
[self.table bind:@"selectionIndexPaths" toObject:self.treeController withKeyPath:@"selectionIndexPaths" options:@{}];
[self.table bind:@"sortDescriptors" toObject:self.treeController withKeyPath:@"sortDescriptors" options:@{}];

Adding a node to the tree's root:

DataNode *node = [[DataNode alloc] init];
node.firstText = [NSString stringWithFormat:@"1 - %d", i1];
node.secondText = [NSString stringWithFormat:@"2 - %d", i1];
NSIndexPath *loc = [NSIndexPath indexPathWithIndex:self.contents.count]; // appends to end of list
[self.treeController insertObject:node atArrangedObjectIndexPath:loc];

Since my NSOutlineView is cell-based, I also had keep implementing the DataSource methods in order to provide the value for the cells, as I was not able to figure out how to make a binding for that:

-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
    return 0;   // never called (due to using NSTreeController)
}

-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
    return nil; // never called (due to using NSTreeController)
}

-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
    return NO;  // never called (due to using NSTreeController)
}

-(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    DataNode *node = [item representedObject];
    return [node valueForKey:tableColumn.identifier];
}

The objectValueForTableColumn method assumes that the table columns' identifiers have been set to firstText and secondText, respectively.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
  • 1
    Apple provides constants for binding names: `NSContentBinding`, `NSSelectionIndexPathsBinding`, `NSSortDescriptorsBinding` etc. – Willeke Mar 14 '17 at 21:38
  • In Swift, `NSContentBinding` is moved to `NSBindingName.content`. All other binding names are equally moved to appropriate place. – eonil Jun 19 '20 at 02:52