With NSTreeController, do I have to manually reload an NSOutlineView when changing the model array?
Asked Answered
M

1

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"];
}
Marquis answered 9/5, 2012 at 23:47 Comment(0)
S
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

Sheba answered 10/5, 2012 at 0:16 Comment(11)
Do you have advice for the OP's specific scenario, where new child nodes don't appear until their grandparents are reloaded?Saar
Almost all cases where the view is out of date until you manually reload are caused by the issue I cited.Sheba
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"].Marquis
I add a new child like this: - (void)addChild:(MyNode *)child { [self willChangeValueForKey:@"childNodes"]; [children addObject:child]; [self didChangeValueForKey:@"childNodes"]; }Marquis
@KenThomases My point is you're giving very generic advice to a specific problem.Saar
@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:].Sheba
@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.Marquis
@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.Marquis
@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).Marquis
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).Marquis
Sorry, no. I don't have any experience with view-based table/outline views.Sheba

© 2022 - 2024 — McMap. All rights reserved.