在 NSOutlineView 中托管 NSView 的图层

Layer hosting NSView within NSOutlineView

我正在尝试创建一个自定义 NSView 来托管 CALayer 层次结构以执行高效显示。此 NSView 然后嵌入到 NSTableCellView 中,该 NSTableCellView 由基于视图的 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 中正确显示?

很难阅读 NSOutlineView 参考文档并找到可能适合您的单元格重用信息。

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

也许有一个有用的提及,隐藏在 NSOutlineViewDelegate 协议中,在基于视图的 NSOutlineView 方法部分中,there is a single mentionoutlineView:didRemoveRowView:forRow: 中:

The removed rowView may be reused by the table, so any additionally inserted views should be removed at this point.

换句话说,当您调用大纲视图的 makeViewWithIdentifier:owner: 时,对于具有特定 ID 的 cellView 或 rowView,您经常 得到一个循环视图。尤其是经常因为崩溃。顺便说一下,that 方法来自 NSTableView 超类,在该参考中,还有 this comment:

This method may also return a reused view with the same identifier that is no longer available on screen. If a view with the specified identifier can’t be instantiated from the nib file or found in the reuse queue, this method returns nil.

因此您可以选择在 didRemoveRowView:forRow 中更改视图层次结构或 niling 属性。然而,隐藏在第三个 cocoa 参考中,对于 NSView,在 prepareForReusethis comment:

的评论中

This method offers a way to reset a view to some initial state so that it can be reused. For example, the NSTableView class uses it to prepare views for reuse and thereby avoid the expense of creating new views as they scroll into view. If you implement a view-reuse system in your own code, you can call this method from your own code prior to reusing them.

所以,TL;DR,您需要实施 prepareForReuse

相关参考文献(大部分)是 NSOutlineViewNSTableCellView 超类

而且,FWIW,有一个 similar question here,提问者似乎表明事情比我想象的还要糟糕,因为 NSOutlineView 在幕后比 NSTableView 更有创意。

在我自己使用大纲视图和嵌入式 NSTextViews 的工作中,我看到了与 expand/collapse/scroll 相关的非常糟糕的渲染问题,我似乎只在 NSOutlineViewDelegate 方法中解决了这些问题。在 iOS 他们帮大家把 makeViewWithIdentifier 重命名为更明确的 dequeueReusableCellViewWithIdentifier.

我终于回答了我的问题。问题不在于我的 TestView 的实现方式。我只是错过了在应用程序中启用 CoreAnimation 支持的步骤之一。相关参考在核心动画编程指南中。

基本上,在 iOS 中,核心动画和图层支持始终默认启用。在 OS X 上,必须以这种方式启用它:

  1. Link 针对 QuartzCore 框架
  2. 通过执行以下操作之一为一个或多个 NSView 对象启用图层支持
    • 在您的 nib 文件中,使用视图效果检查器为您的视图启用图层支持。检查器显示所选视图及其子视图的复选框。建议您尽可能在 window 的内容视图中启用图层支持
    • 对于您以编程方式创建的视图,调用视图的 setWantsLayer: 方法并传递 YES 值以指示视图应使用图层。

一旦我在任何 NSOutlineView 的父级上启用层支持,各种故障就解决了。

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

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

所以,问题是:您如何安排视图与其父视图一起移动或调整大小?你在使用自动布局吗?如果是这样,您是否关闭了它的 translatesAutoresizingMaskIntoConstraints?如果两者都是,您对视图设置了什么限制?如果两者都不是,您是如何在其父视图中定位视图的?你设置的是什么框架?此外,超级视图是否配置为自动调整其子视图的大小(可能是,因为这是默认设置)?您的观点是 autoresizingMask

您还可以在自定义视图 class 中覆盖 -setFrameOrigin:-setFrameSize: 并调用 super。此外,添加日志记录以显示何时发生以及新的 frame rect 是什么。当您展开或折叠行时,您的视图是否按预期移动?