8

I have a tree-like model I'd like to show in an NSOutlineView using an NSTreeController.

I was able to set up the bindings and everything works fine as long as I use the NSTreeController's insert and remove functions to change my model tree. If I try to insert or remove from the model tree directly, in some cases the NSOutlineView isn't updating.

If I insert an object into an expanded group of objects, it works:

Inserting node

New node showing

But if I try to add the first object to a node, that had no children before, nothing happens. The disclosure triangle isn't appearing, so I can't expand it to see the new node.

Inserting new child

enter image description here

If I hover over that node with a new object, it is expanded and I can add the second child with no problems. But the triangle is still invisible:

enter image description here

Finally if I close the parent of all these nodes and open them again (triggering a reload) the triangle suddenly appears:

enter image description here

That's why I was wondering if I had to manually reload the NSOutlineView's rows to make the triangle visible, or if I'm messing up something? Thanks!!

UPDATE:

In my Node class I add a new child like this:

- (void)addChild:(MyNode *)child {
    [self willChangeValueForKey:@"childNodes"];
    [children addObject:child];
    [self didChangeValueForKey:@"childNodes"];
}

And I implemented these too (which I set in IB for my NSTreeController):

- (NSArray *)childNodes {
    return [NSArray arrayWithArray:children];
}

- (NSInteger)countOfChildNodes {
    return [children count];
}

- (BOOL)nodeIsLeaf {
    return [children count] < 1;
}

I know that this (especially childNodes) aren't very optimized, but I'm only experimenting at the moment as in the final version my children will be stored in a C array.

UPDATE 2:

I also tried sending KVO notifications for the other 2 properties too, but that didn't help either.

- (void)addChild:(MyNode *)child {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self willChangeValueForKey:@"nodeIsLeaf"];
    [self willChangeValueForKey:@"countOfChildNodes"];
    [self willChangeValueForKey:@"childNodes"];
    [children addObject:child];
    [self didChangeValueForKey:@"childNodes"];
    [self didChangeValueForKey:@"countOfChildNodes"];
    [self didChangeValueForKey:@"nodeIsLeaf"];
}
DrummerB
  • 39,814
  • 12
  • 105
  • 142

1 Answers1

2

You have to make sure that all updates to your model are performed in a Key-Value Observing-compliant manner.

Cocoa Bindings Programming Topics: Troubleshooting Cocoa Bindings

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Do you have advice for the OP's specific scenario, where new child nodes don't appear until their grandparents are reloaded? – paulmelnikow May 10 '12 at 00:21
  • Almost all cases where the view is out of date until you manually reload are caused by the issue I cited. – Ken Thomases May 10 '12 at 00:26
  • I spend the last 3 hours verifying that I was updating my children arrays in a KVO compliant manner and I don't understand why NSTreeController doesn't bother examining the isLeaf property when I update the children array and use `[self will/didChangeValueForKey:@"childNodes"]`. – DrummerB May 10 '12 at 00:27
  • I add a new child like this: `- (void)addChild:(MyNode *)child { [self willChangeValueForKey:@"childNodes"]; [children addObject:child]; [self didChangeValueForKey:@"childNodes"]; }` – DrummerB May 10 '12 at 00:29
  • @KenThomases My point is you're giving very generic advice to a specific problem. – paulmelnikow May 10 '12 at 00:30
  • 1
    @noa, there weren't enough specifics in the question to do anything else. There are now. @DrummerB, since `-nodeIsLeaf` and `-countOfChildNodes` both change whenever `children` changes, you also have to inform KVO of that. As things stand, you are effectively modifying those properties in a non-KVO-compliant manner. You can fix that with additional calls to `-will/didChange...` or you can use the technique provided by `+[NSKeyValueObserving keyPathsForValuesAffectingValueForKey:]`. – Ken Thomases May 10 '12 at 01:48
  • @KenThomases I assumed that that wasn't necessary (since the NSTreeController knows about the keys and could check them when I change `childNodes`), but nonetheless I tried calling `-will/didChange...` for `nodeIsLeaf` and `countOfChildNodes` (see update). I got the same results. – DrummerB May 10 '12 at 11:07
  • @KenThomases If I log the 3 getters I see that `nodeIsLeaf` is called all the time during the drag operation. Presumably to know if it can expand the node I'm hovering over, which it does once there is at least 1 child, but still doesn't show a triangle. Once I drop the new node `nodeIsLeaf` is called 3 times (returning 3 times NO). Shouldn't the NSTreeController realize by now that it should make the NSOutlineView show a triangle? The other getters aren't called. – DrummerB May 10 '12 at 11:08
  • @KenThomases According to the documentation `isLeaf` and `countOf..` are optional and `childNodes` will be used to determine how many children there are. "If there are 0 child objects, the triangle is not displayed, otherwise it is". However if I remove the 2 optional getters from the implementation and the NSTreeController's properties I still don't get the triangle. This time once I drop the new node `childNodes` is called once (returning the array with the 1 new child). – DrummerB May 10 '12 at 11:08
  • I just realized that changing the NSOutlineView from being view-based back to cell-based solves the problem. Do you have any idea why that could be? I know I have to set up different bindings (did them according to the documentation). – DrummerB May 10 '12 at 11:31
  • Sorry, no. I don't have any experience with view-based table/outline views. – Ken Thomases May 10 '12 at 15:39