tbo*_*odt 15 cocoa nsoutlineview nstreecontroller
我有一个NSOutlineView显示目录层次结构.它绑定到一个NSTreeController绑定到我的类来管理文件系统节点.当发生文件系统事件时,我在children密钥路径上触发KVO通知,这会导致大纲视图更新.但是当它更新时,它会突然滚动到最顶端.我希望滚动位置保持不变.有任何想法吗?
这是FS事件发生时运行的代码:
- (void)URLWatcher:(CDEvents *)URLWatcher eventOccurred:(CDEvent *)event {
[self willChangeValueForKey:@"children"];
children = nil; // this will refreshed next time children is called
[self didChangeValueForKey:@"children"];
}
Run Code Online (Sandbox Code Playgroud)
这是在模型中,所以我无法访问该视图.
我没有测试或试过以下,但我想我还是试了一下.
首先,使用NS*控制器管理任何复杂的NSTableView或NSOutlineView都很痛苦,并且牺牲了精确的控制以换取简单性.如果您发现自己在这种情况下的行为,请考虑在您自己的自定义控制器中实现数据源和委托协议(NSTableViewDataSource,NSTableViewDelegate或NSOutlineViewDataSource,NSOutlineViewDelegate).
其次,Warren Burton关于发射KVO通知的评论是相关的,因为你应该告诉负责的控制器(你的NSTreeController)有关变化,因为它是控制(和观察)该集合的人.更重要的是,您应该直接使用NSTreeController的add/insert/remove方法.你现在这样做的方式(每次你取消整个结构然后重置它)将导致整个树重新加载.由于控制器正在观察该集合,它告诉大纲视图刷新自己,可能允许它首先看到空的轮廓,然后是稍后的大纲的进一步扩展版本,这将失去用户的扩展状态等.修改通过树控制器的模型将允许更智能,更有效的视图更新.
第三,您可以考虑通过继承NSTreeController并重写add/insert/remove方法来利用上面的第二点来执行以下操作:
-visibleRect.-scrollRectToVisible:.您可能必须通过在主线程上调度它来延迟步骤3中的调用(因此它在当前通过运行循环的行程之后发生).或者,替换步骤3替换步骤3并在某处存储可见的rect并实现NSOutlineViewDelegate -outlineView:didAdd/RemoveRowView:forRow:方法以检查此标志-scrollRectToVisible:如果rect非零则从那里调用(记得将其重置为NSZeroRect,这样它就不会尝试调整滚动每次添加或删除轮廓行时).
Clunky,但是一个合理的(?)路径,允许你保留NSTreeController.
第四,或者(和我去的方式),您可以完全删除NSTreeController并在您自己的控制器类中实现NSOutlineViewDataSource(和NSOutlineViewDelegate)协议,并让该控制器直接处理添加到树结构或从树结构中删除.然后它变得更清洁,因为你不必担心KVO时间.在任何添加节点时,您可以注意到可见的rect,更新大纲视图,然后在同一方法中调整所有滚动并跳过运行循环.
我希望这有所帮助,而且不是太漫无边际.:-)
我可以建议您保留当前的滚动视图偏移量,并在发送 KVO 通知并更新大纲视图后恢复它。
- (void)updateOutlineView:(NSOutlineView *)outlineView
{
// first save offset
NSScrollView *scrollView = [outlineView enclosingScrollView];
NSClipView *clipView = [scrollView contentView];
NSPoint offset = clipView.bounds.origin;
// send KVO notification of the 'children' keypath
// ...
// restore offset
[clipView scrollPoint:offset];
}
Run Code Online (Sandbox Code Playgroud)
看一下滚动点是绝对值。您可以根据更新的轮廓视图高度计算目标点。
//... before the notification sent
CGFloat height = [[[[scrollView documentView] frame] size] height];
CGFloat yValue = offset.y / height;
//... after the outline view updated
CGFloat newHeight = [[[[scrollView documentView] frame] size] height];
offset.y = newHeight * yValue;
[clipView scrollPoint:offset];
Run Code Online (Sandbox Code Playgroud)