在NSOutlineView中托管NSView的层级结构

15

我正在尝试创建一个自定义的 NSView,用于承载一个 CALayer 层级以实现高效的显示。然后将此 NSView 嵌入到一个 NSTableCellView 中,并由 View-Based NSOutlineView 显示。

问题在于每当我展开或折叠一个项目时,所有行都会移动,但层的内容仍然显示在改变大纲之前的位置。

滚动 NSOutlineView 似乎会刷新层,然后它们会在那一点上与它们的行同步。

我已经使用 Instruments 调试了这种行为,它似乎是滚动引发了布局操作,该操作使用 setPosition: 调用更新了层,这应该在展开或折叠项时发生。

以下是一个简单的层托管 NSView 子类的示例代码。

@interface TestView : NSView

@end

@implementation TestView

- (instancetype)initWithFrame:(NSRect)frameRect
{
    self = [super initWithFrame:frameRect];
    CAShapeLayer* layer = [CAShapeLayer layer];
    layer.bounds = self.bounds;
    layer.position = CGPointMake(NSMidX(self.bounds), NSMidY(self.bounds));
    layer.path = [NSBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
    layer.fillColor = [NSColor redColor].CGColor;
    layer.delegate = self;
    self.layer = layer;
    self.wantsLayer = YES;
    return self;
}

@end

我尝试了很多可能的解决方案,但是我找不到任何有趣的方法可以在NSView实例上调用以覆盖调用[self.layer setNeedsDisplay][self.layer setNeedsLayout]。我还尝试了对CALayer本身的各种设置器,例如:

layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
layer.needsDisplayOnBoundsChange = YES;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;

有人能帮我弄清楚如何使这个图层在 NSOutlineView 中正确显示吗?

3个回答

5
我最终找到了答案。问题并不在于我的TestView的实现方式,而是我错过了启用应用程序内核动画支持的某一步骤。相关参考资料在Core Animation编程指南中。
基本上,在iOS中,核心动画和层支持始终默认启用。在OS X上,需要按照以下方式启用:
1.链接QuartzCore框架 2.通过执行以下操作之一,为一个或多个NSView对象启用层支持:     a.在nib文件中,使用View Effects检查器启用视图的层支持。检查器显示所选视图及其子视图的复选框。建议在窗口的内容视图中尽可能启用层支持。     b.对于您以编程方式创建的视图,请调用视图的 setWantsLayer:方法,并传递YES值,表示该视图应使用层。
一旦我在任何一个NSOutlineView的父级上启用层支持,各种故障都得到了解决。

感谢您!我也遇到了同样的问题(使用层创建自定义进度旋转器),并且展开/折叠单元格时无法移动旋转器。对我而言,我只需要在“大纲视图”上启用“层支持”。之后,所有问题都解决了。 - Kyle
我很高兴这对你有用! :) 我知道我搜索了相当长时间才找到这个答案! - Dalzhim

0

阅读NSOutlineView参考文档并找到关于单元格重用的信息可能会让你感到困难。

你可能已经看过outlineViewItemDidCollapse:,但它对我们的问题来说有点无用,因为它没有指向NSView的指针,这是因为它比基于视图的大纲视图旧。

也许唯一有帮助的提及是在NSOutlineViewDelegate协议中埋藏的,在基于视图的NSOutlineView方法部分中,在outlineView:didRemoveRowView:forRow:中有一个提及:

删除的rowView可能会被表格重用,因此任何额外插入的视图都应在此时删除。

换句话说,当您调用大纲视图的makeViewWithIdentifier:owner:方法时,对于具有特定ID的cellView或rowView,您通常会获得一个已回收的视图。由于折叠的原因,尤其经常发生这种情况。顺便说一下,该方法来自NSTableView的超类,并且在该参考文献中还有this comment

该方法也可能返回具有相同标识符的重复使用视图,该视图在屏幕上不再可用。如果无法从nib文件实例化具有指定标识符的视图,也无法在重用队列中找到该视图,则此方法将返回nil。

所以你可以选择在 didRemoveRowView:forRow 中更改视图层次结构或将属性设置为 nil。然而,在第三个 Cocoa 参考文献中,即关于 NSView 的评论中,有这样一段话:this comment:

该方法提供了一种重置视图到某个初始状态的方式,以便可以重复使用它。例如,NSTableView 类使用它来准备视图以便重复使用,并避免创建新视图的开销,因为它们滚动到视图中。如果您在自己的代码中实现了视图重用系统,则可以在重复使用之前从自己的代码中调用此方法。

所以,简而言之,你需要实现 prepareForReuse

相关参考文献主要是 超类,包括 NSOutlineViewNSTableCellView

顺便说一句,有一个类似的问题在这里,问者似乎表明情况比我想象的更糟,因为NSOutlineView在幕后比NSTableView更有创意。

在我自己关于大纲视图和嵌入式NSTextViews的工作中,我遇到了与展开/折叠/滚动相关的极其糟糕的渲染问题,但我似乎只是通过NSOutlineViewDelegate方法解决了这些问题。在iOS上,他们将makeViewWithIdentifier重命名为更明确的dequeueReusableCellViewWithIdentifier,让所有人都受益了。


我已经尝试按照你的建议实现了prepareForReuse,但这并不足以解决问题。 一些调试显示,在处理超出可见区域的内容时,该方法被调用。而在其他情况下,该方法根本没有被使用,这证明这不可能是解决方案。问题实际上在于NSOutlineView执行的展开和折叠动画不能正确处理子图层。我也看到了你指向的其他SO问题,但那个问题还没有得到令人满意的答复。 - Dalzhim

0

您不应该为任何祖先视图(如大纲视图)启用层支持。

根据我的经验,立即分配给视图的图层(而不是子层)不需要设置其边界、位置或自动调整大小掩码。它会自动跟踪视图的边界。实际上,我会避免设置这些属性,以防万一破坏了与视图边界矩形的自动同步。

那么问题来了:您是如何安排视图随其父视图移动或调整大小的呢?您使用了自动布局吗?如果是这样,您是否关闭了其translatesAutoresizingMaskIntoConstraints?如果两者都是,您在视图上设置了哪些约束?如果两者都不是,您是如何在其父视图中定位视图的?您设置了什么框架?此外,父视图是否配置为自动调整其子视图的大小(可能是,因为这是默认值)?您的视图的autoresizingMask是什么?

您还可以在自定义视图类中覆盖-setFrameOrigin:-setFrameSize:,并调用超级方法。此外,添加日志以显示发生的时间以及新的框架矩形是什么。当您展开或折叠行时,您的视图是否按预期移动?


我展示的TestView代码已经完成。这是一个链接到苹果的OutlineView演示项目,我已经在其中添加了TestView。这个项目很好用,直到你从Application.xib中的窗口内容视图中删除该层为止。http://filebin.ca/1zm0kIYSuMNo/OutlineView.zip - Dalzhim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接