首页 > 解决方案 > NSImageView 总是显示在任何其他类型的视图之上

问题描述

我正在开发一个数据库应用程序,它具有与 Interface Builder 非常相似的图形编辑器。然而,与 IB 不同的是,这个编辑器可以从编辑器模式切换到实时模式,在这种模式下,用户界面是完全可操作的(可以单击按钮、编辑文本等)。为此,图形编辑器使用标准的 Appkit 接口类—— NSButton、NSTextView 等。编辑器本身是用 NSView 的自定义子类实现的。所有的用户界面元素都是这个自定义 NSView 的子视图,新元素是使用addSubview:方法,使新元素成为最顶层的可见元素(注意——视图不是图层支持的,只是常规视图)。用户还可以使用Bring-to-Front 和Send-to-Back 命令来更改子视图的顺序。这部电影展示了两个重叠的 NSButton 元素(出于说明目的,当然通常您不会重叠它们),以及程序如何重新排列子视图以更改用户界面元素的 Z 顺序。

带到前面,发送到后面

问题是,这适用于除NSImageView之外的所有类型的界面元素。在电影之前有两个元素,一个NSButton和一个NSImageView。NSButton 实际上一直在“顶部”,NSImageView 元素应该出现在按钮后面,但无论子视图的顺序如何,NSImageView 总是出现在顶部。

NSImageView 总是在顶部

如果有两个重叠的 NSImageView 对象,它们之间的可见堆叠顺序是不可预知的,但无论子视图的顺序如何,它们总是会出现在所有其他对象之上。

一个可能有用的线索是,如果我实现自己的自定义视图,直接在它的drawRect:方法中绘制图像,那效果很好。所以这是一种可能的解决方案,但我很不情愿,因为这意味着重新实现 NSImageView 通常负责的大量有用功能,其中一些非常复杂,例如支持动画 GIF 显示。除了这个分层/z-order 问题之外,关于 NSImageView 的其他一切都可以正常工作。

也许 NSImageView 在我没有要求的情况下使用了图层支持,所以它没有与我的其他对象正确混合?我找不到任何表明这一点的文档。我没有链接 QuartzCore 框架。

这是将 NSImageView 元素作为子视图添加到图形编辑器视图的代码。

- (void)objectDidAppearBelow:(NSView *)nextView
{
    FormView * formView = [FormWindowController currentFormView]; // get view element will be placed into
    NSScrollView * imageContainer = [[NSScrollView alloc] initWithFrame:insideBorderRect];
    ImageView * ixView = [[ImageView alloc] initWithFrame:[self insideFormObjectBorder:objectRectangle]];
    [ixView setOwnerObject:self];
    [imageContainer setDocumentView:ixView];    
    [imageContainer setAutoresizesSubviews:YES];
    [shapeView addSubview:imageContainer Below:nextView];
    imageDocumentView = ixView; // save weak reference to image view so it can be manipulated
}

在其他地方,NSButton(按钮、单选按钮等的几种变体)、NSTextView、NSTableView(用于列表和矩阵)、NSSlider、NSScroller、NSSegmentedControl 甚至 WebView 的代码几乎相同。所有其他对象都可以正常使用重叠对象,包括 WebView,只有 NSImageView 不能按预期工作。

供我参考,这是 Panorama X 问题跟踪器中的 #429。

标签: cocoaappkit

解决方案


我在 WWDC 2018 上与 Apple 工程师讨论了这个问题。事实证明,正如我所怀疑的那样,在某些情况下,即使你没有要求 Appkit 也会为 NSImageView 使用层支持!所以最好的解决方案是将所有视图切换到图层支持(这将在 Mojave 中自动发生)。

在这种特殊情况下,NSImageView 位于 NSScrollView 中,我没有提到它,因为我认为它不重要(我的错)。事实证明,这就是 Appkit 认为使用层支持是一个好主意(优化滚动)的情况。所以解决这个问题的另一种方法是子类化 NSImageView(我已经因为其他原因做了)并添加这个方法(由一个喜欢保持不可信的苹果工程师当场编写)。

+ (BOOL)isCompatibleWithResponsiveScrolling {
    if (NSAppKitVersionNumber <= 1561. /* NSAppKitVersionNumber10_13 */) {
        return NO;
    } else {
        return YES;
    }
}

我确信这都是公开的、记录在案的 API 的一部分,尽管文档很少(出乎意料)。在 OS X 10.9发行说明中的​​新增功能中有一些关于响应式滚动的讨论。使用 NSAppKitVersionNumber 检查是为了确保在 Mojave 上运行时关闭此补丁,因为一切都是分层支持的。


推荐阅读