【发布时间】:2009-01-21 17:44:51
【问题描述】:
我正在开发一个 Cocoa 应用程序,我遇到了一种情况,我希望两个 NSView 对象重叠。我有一个父 NSView,它包含两个子视图(NSView A 和 NSView B),每个子视图都可以有自己的几个子视图。
是否有适当的方法来处理这种重叠? NSView B 总是“高于” NSView A,所以我希望 NSView A 的重叠部分被屏蔽掉。
【问题讨论】:
标签: cocoa
我正在开发一个 Cocoa 应用程序,我遇到了一种情况,我希望两个 NSView 对象重叠。我有一个父 NSView,它包含两个子视图(NSView A 和 NSView B),每个子视图都可以有自己的几个子视图。
是否有适当的方法来处理这种重叠? NSView B 总是“高于” NSView A,所以我希望 NSView A 的重叠部分被屏蔽掉。
【问题讨论】:
标签: cocoa
克里斯,唯一的解决方案是使用 CALayers。这绝对是唯一的解决方案。
NSViews 在 OSX(2010 年 9 月)中被彻底破坏:siblings 无法正常工作。一个或另一个将随机出现在顶部。
重复一遍,问题出在兄弟姐妹上。
对此进行测试:使用 NSViews 和/或 nsimageviews。制作一个视图是一个大图像(例如 1000x1000)的应用程序。在视图中,到处放三四个小图像/NSView。现在在上面放另一个 1000x1000 的大图像。反复构建和启动应用程序——你会发现它完全坏掉了。通常下面的(小)层会出现在大覆盖层的顶部。如果您在 NSViews 上打开 layer-backing,无论您尝试什么组合,它都无济于事。所以这是最终的测试。
您必须放弃 NSViews 并使用 CALayers,仅此而已。
CALayers 唯一的烦恼是你不能使用 IB 来设置你的东西。您必须在代码中设置所有图层位置,
yy = [CALayer layer];
yy.frame = CGRectMake(300,300, 300,300);
只做一个 NSView,它的唯一目的是拿着你的第一个 CALayer(可能叫做'rear'),然后把你所有的 CALayer 放在后面。
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 会 不强制兄弟姐妹之间剪裁 意见或保证正确 失效和绘图行为时 兄弟视图重叠。
他们下面的句子……
如果你想绘制一个视图 在另一个视图的前面,你应该做 前视图一个子视图(或 后代)的后视图。
在很大程度上是荒谬的(您不一定要用 subs 替换兄弟姐妹;并且上面测试中描述的明显错误仍然存在)。
所以它是 CALayers!享受吧!
【讨论】:
addSubview:positioned:relativeTo 和 sortSubviewsUsingFunction:context 的意义何在?对于前者,文档指出:“在接收者的子视图中插入一个视图,使其立即显示在另一个视图的上方或下方。”如果 Cocoa 在绘图时没有强制执行排序,那么排序子视图有什么意义呢?事件处理?
如果您的应用程序仅支持 10.5,请为视图打开层,它应该可以正常工作。
如果您打算支持 10.4 及更低版本,则需要找到一种不让视图重叠的方法,因为重叠的同级视图是未定义的行为。正如 View Programming Guide 所说:
出于性能原因,Cocoa 不强制在同级视图之间进行剪辑,也不保证在同级视图重叠时正确的失效和绘制行为。如果你想在另一个视图前面绘制一个视图,你应该使前视图成为后视图的子视图(或后代)。
我见过一些技巧,有时可以让它发挥作用,但这不是你可以依赖的东西。您需要将视图 A 设置为视图 B 的子视图,或者创建一个处理这两项职责的巨型视图。
【讨论】:
有一种方法可以在不使用CALayers 的情况下做到这一点,而我一直在开发的应用程序可以证明这一点。创建两个窗口并使用它:
[mainWindow addChildWindow:otherWindow ordered:NSWindowAbove];
要删除“otherWindow”,请使用:
[mainWindow removeChildWindow:otherWindow];
[otherWindow orderOut:nil];
你可能会想要使用窗口的标题栏:
[otherWindow setStyleMask:NSBorderlessWindowMask];
【讨论】:
为了确保NSView B 始终与NSView A 重叠,请确保在添加subview: 时使用正确的NSWindowOrderingMode
[parentView addSubview:B positioned:NSWindowAbove relativeTo:A];
您还应该记住,如果视图 B 100% 不透明,则不会要求 A 的隐藏部分重绘。
如果要移动子视图,还需要确保调用
-setNeedsDisplayInRect: 用于您正在发现的视图区域。
【讨论】:
正如 Nate 所写,可以使用:
self.addSubview(btn2, positioned: NSWindowOrderingMode.Above, relativeTo: btn1)
但是,只要您通过“needDisplay = true”调用要求任一视图自行重绘,视图的顺序就不会得到尊重
兄弟姐妹不会得到 drawRect 调用,只有视图直接层次结构会。
为了解决这个问题,我不得不深入挖掘。大概一周的研究,我把我的发现传播到了几篇文章中。最后的突破在这篇文章中:http://eon.codes/blog/2015/12/24/The-odd-case-of-luck/
请注意,虽然这个概念很难理解,但它很有效,而且效果很好。这是最终结果和支持它的代码,github repo 的链接等:http://eon.codes/blog/2015/12/30/Graphic-framework-for-OSX/
【讨论】: