I have an NSOutlineView
that is displaying a directory hierarchy. It's bound to an NSTreeController
, which is bound to my class that manages file system nodes. When a filesystem event occurs, I fire a KVO notification on the children
keypath, which causes the outline view to update. But when it updates, it suddenly scrolls up to the very top. I want the scrolling position to stay the same. Any ideas?
Here's the code that runs when an FS event occurs:
- (void)URLWatcher:(CDEvents *)URLWatcher eventOccurred:(CDEvent *)event {
[self willChangeValueForKey:@"children"];
children = nil; // this will refreshed next time children is called
[self didChangeValueForKey:@"children"];
}
This is in the model, so I can't to access the view.
I haven't tested or attempted the following, but thought I'd give it a shot anyway.
First, managing anything complicated with NSTableView or NSOutlineView with an NS*Controller is painful and sacrifices precise control in exchange for simplicity. If you find yourself fighting behavior in this situation, consider implementing the datasource and delegate protocols (NSTableViewDataSource, NSTableViewDelegate or NSOutlineViewDataSource, NSOutlineViewDelegate) in your own custom controller.
Second, Warren Burton's comment regarding firing KVO notifications is pertinent, since you should be telling the responsible controller (your NSTreeController) about the changes, since it's the one controlling (and observing) that collection anyway. More to the point, you should be using NSTreeController's add/insert/remove methods directly. The way you're doing it now (whacking the entire structure each time you nullify then reset it later) will cause the entire tree to reload. Since the controller is observing that collection, it's telling the outline view to refresh itself, possibly allowing it to see an empty outline first, then a further-expanded version of the outline later, which will lose your user's expansion states, etc. Modifying the model via the tree controller will allow for smarter, more efficient view updates.
Third, you could consider taking advantage of my second point above by subclassing NSTreeController and overriding the add/insert/remove methods to do the following:
-visibleRect
.-scrollRectToVisible:
.You may have to delay the call in step 3 by scheduling it on the main thread (so it happens after the current trip through the run loop). Or, alternately, replace step 3 with storing the visible rect somewhere and implementing the NSOutlineViewDelegate -outlineView:didAdd/RemoveRowView:forRow:
methods to check for this flag THEN call -scrollRectToVisible:
from there if the rect is non-zero (remember to reset it to NSZeroRect so it doesn't attempt to adjust scroll every time an outline row is added or removed).
Clunky, but a reasonable(?) path that allows you to keep the NSTreeController.
Fourth, alternatively (and the way I'd go), you could drop NSTreeController entirely and implement NSOutlineViewDataSource (and NSOutlineViewDelegate) protocols in your own controller class, and let that controller directly handle adding to or removing from your tree structure. Then it becomes cleaner since you don't have to worry about KVO timings. On any adding of nodes, you can note the visible rect, update the outline view, then adjust scroll all within the same method and trip through the run loop.
I hope this helps and wasn't too rambling. :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With