有没有正确的方法来处理重叠的NSView兄弟视图?

16
我正在开发一款Cocoa应用程序,遇到了一个情况:我希望有两个NSView对象重叠。 我有一个包含两个子视图(NSView A和NSView B)的父NSView,它们各自可以有几个自己的子视图。
有没有适当的方法来处理这种重叠? NSView B始终会“盖住”NSView A,因此我想要NSView A被覆盖的部分被掩盖。
5个回答

13

Chris,唯一的解决方案是使用CALayers。这绝对是唯一的解决方案。

在OSX中,NSViews存在严重问题(2010年9月):兄弟节点无法正常工作。其中一个或另一个将随机出现在顶部。

再次强调,问题出在兄弟节点上。

测试方法:使用NSViews和/或nsimageviews。创建一个视图,其中包含一个大图像(比如1000x1000)。在视图中,放置三到四个小图像/NSViews。然后在顶部放置另一个大的1000x1000图像。反复构建和启动应用程序-你会发现它是明显有问题的。通常,下面(小)层会出现在覆盖层的顶部。如果在NSViews上开启layer-backing,无论尝试什么组合都没有帮助。所以这就是明确的测试。

您必须放弃NSViews并使用CALayers。

唯一让人烦恼的是,您无法使用IB设置您的内容。您必须在代码中设置所有层位置。

yy = [CALayer layer];
yy.frame = CGRectMake(300,300, 300,300);

创建一个仅包含第一个CALayer(例如名为“rear”)的NSView,并将所有CALayer放置在rear中。
rear = [CALayer layer];
rear.backgroundColor = CGColorCreateGenericRGB( 0.75, 0.75, 0.75, 1.0 );

[yourOnlyNsView setLayer:rear]; // these two lines must be in this order
[yourOnlyNsView setWantsLayer:YES]; // these two lines must be in this order

[rear addSublayer:rr];
[rear addSublayer:yy];
     [yy addSublayer:s1];
     [yy addSublayer:s2];
     [yy addSublayer:s3];
     [yy addSublayer:s4];
[rear addSublayer:tt];
[rear addSublayer:ff];

一切都完美地运作,您可以嵌套和分组任何内容,并且所有内容都能够无缝地与其它元素正确地出现在上/下方,不管您的结构有多么复杂。稍后您可以对层进行任何操作或以典型方式重新排列物品。

-(void) shuff
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:0.0f]
              forKey:kCATransactionAnimationDuration];
if ..
    [rear insertSublayer:ff below:yy];
else
    [rear insertSublayer:ff above:yy];
[CATransaction commit];
}

每次执行操作时,烦人的“在零秒内”包装器唯一存在的原因是为了防止免费提供给您的动画效果-除非您需要动画效果!

顺便说一下,在苹果公司的以下引用中:

出于性能原因,Cocoa不会在同级视图之间强制裁剪,也不保证当同级视图重叠时正确地使其无效和绘制。

他们接下来的句子...

如果您想要将一个视图绘制在另一个视图的前面,则应将前置视图设置为后置视图的子视图(或后代)。

在很大程度上是毫无意义的(您不能总是用子视图替换同级视图;并且在上述测试中描述的明显错误仍然存在)。

所以这就是CALayers!享受吧!


1
谢谢你的精彩回答,让我明白了如何移除我们软件中存在两年之久的可怕代码混乱! - joerick
经过一些调查,我无法真正地消除这个丑陋的东西,但至少现在我对它的理解更深入了。干杯! - joerick
有点晚了,但我正在尝试重叠兄弟视图,并遇到了这个线程。如果认为重叠的兄弟NSViews是有问题的,那么NSView方法addSubview:positioned:relativeTosortSubviewsUsingFunction:context有什么意义呢?前者的文档说明如下:“将视图插入接收器的子视图中,以便它立即显示在另一个视图的上方或下方。”如果Cocoa在绘制时没有强制执行排序,那么排序子视图有什么意义呢?事件处理? - kennyc
如果您想让一个视图在另一个视图的前面绘制,您应该将前置视图作为后置视图的子视图(或后代)。我相信兄弟姐妹的排序纯粹是为了方便。(不确定为什么。) - Fattie

6
如果您的应用程序仅限于10.5,请为视图打开层,它应该可以正常工作。
如果您想支持10.4及以下版本,则需要找到一种方法以避免视图重叠,因为重叠的兄弟视图是未定义的行为。如视图编程指南所述:
"出于性能原因,Cocoa不会在兄弟视图之间执行剪切或保证兄弟视图重叠时正确的无效和绘图行为。如果您想使一个视图在另一个视图前面绘制,您应该将前景视图设置为后景视图的子视图(或派生类)。"
我见过一些可以有时候使其工作的黑客技巧,但这不是您可以依赖的任何东西。您需要将View A 设置为View B的子视图,或者创建一个处理它们两个职责的大型视图。

1
@Joe:没错,我们在多个应用程序中使用了它。你是否已经为包含重叠子视图(重要)的视图启用了层次后备?这将使子视图自动进行层次后备,现在这些视图可以毫无问题地重叠。如何定义哪个视图在前面?在添加视图时,可以使用addSubview:positioned:relativeTo:(如果需要,重新添加它们),或者我认为操作z顺序也可以(这是一种hack,你不应该为支持NSViews的层次后备而操纵它)。 - Mecki
是的,它在10.5版本中可以工作。一位苹果工程师在2009年表示,文档已经过时了。请参见https://dev59.com/lmgv5IYBdhLWcg3wSewe#10720063。 - Johannes Fahrenkrug
各位,你们没有仔细阅读:这与兄弟姐妹关系有关,而不是父子关系。干杯! - Fattie

3

有一种方法可以在不使用 CALayers 的情况下实现,而我正在开发的一个应用程序可以证明这一点。创建两个窗口并使用以下代码:

[mainWindow addChildWindow:otherWindow ordered:NSWindowAbove];

要移除“otherWindow”,请使用:
[mainWindow removeChildWindow:otherWindow];

[otherWindow orderOut:nil];

您可能希望一并获取窗口的标题栏:

[otherWindow setStyleMask:NSBorderlessWindowMask];

2
如Nate所写,可以使用以下方法:
self.addSubview(btn2, positioned: NSWindowOrderingMode.Above, relativeTo: btn1)

然而,只要你通过"needDisplay = true"调用任何一个视图来重绘自己,视图的顺序就不会被尊重。

兄弟视图将不会收到drawRect调用,只有视图的直接层次结构会。

更新1

为了解决这个问题,我必须深入挖掘。可能需要一周的研究,并且我将我的发现分散在几篇文章中。最终突破在这篇文章中:http://eon.codes/blog/2015/12/24/The-odd-case-of-luck/

更新2

但请注意,这个概念很难理解,但它非常有效。这里是最终结果和支持它的代码,以及指向Github存储库等的链接:http://eon.codes/blog/2015/12/30/Graphic-framework-for-OSX/


1
为了确保NSView B始终覆盖在NSView A之上,添加subview:时,请确保使用正确的NSWindowOrderingMode
[parentView addSubview:B positioned:NSWindowAbove relativeTo:A];

你还需要记住,如果视图B是100%不透明的,那么A的隐藏部分将不会被要求重新绘制。

如果你正在移动子视图,你还需要确保调用-setNeedsDisplayInRect:来揭示视图中的区域。


Nate,你在这里说的并不相关。Chris问的是SIBLING视图的特定情况,在OSX中处理得非常糟糕。(如果你从iFone转移到OSX,这会让你非常愤怒!) - Fattie

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