如何使一个 NSView 移动到所有 NSView 的最前面?

40

我有一个超级视图,它有两个子视图。这些子视图是重叠的。

每当我从菜单中选择一个视图时,相应的视图应该成为前景视图并处理操作。 也就是说,它应该是最前面的子视图。

acceptsFirstResponder 放弃所有工作都很好。但是鼠标按下事件被发送到了设置的最上面的子视图。

敬礼, Dhana


注意:出于性能原因,Cocoa不在兄弟视图之间执行裁剪...此外!当兄弟视图重叠时,Cocoa不能保证正确的失效和绘制行为。正如手册中所述:“如果要使一个视图在另一个视图前面绘制,则应将前景视图设置为后台视图的子视图(或后代)。”(来自OSX文档中的View编程指南) - Fattie
7个回答

41

这里有另一种更清晰简洁的方法来完成这个任务:

[viewToBeMadeForemost removeFromSuperview];
[self addSubview:viewToBeMadeForemost positioned:NSWindowAbove relativeTo:nil];
根据这个方法的文档,当你使用 relativeTo:nil 时,视图会添加在所有同级视图的上面(或下面,使用 NSWindowBelow)。

5
上述方法存在问题。首先,当你发送removeFromSuperview到一个视图时,它将被释放。因此,你需要先保留该视图,然后稍后再用一个release来平衡原始保留。其次,你还需要移除和添加视图到层级结构中,这可能会带来开销,特别是对于复杂的视图层级结构。还有一种避免从视图层级中移除和添加视图的方法,可以使用NSView内置的子视图排序函数,见下文。 - Dalmazio
我认为这两行代码仍在同一个事件循环中,因此在第二行之前不会释放viewToBeMadeForemost。 - Li Fumin
1
@LiFumin,那是不正确的。你假设removeFromSuperview会自动释放视图,或者其他代码已经将其自动释放了。根据-removeFromSuperview: 的文档,“视图也被释放;如果您计划重用它,请在发送此消息之前保留它,并在将其作为另一个NSView的子视图添加时适当地释放它。” Dalmazio是正确的,必须保留该视图。我想这种解决方案在打破约束等方面也存在问题。它不应该成为被接受的答案。 - bhaller
1
另一个问题:从superview中移除会释放约束。 - Confused Vorlon
如果您移除了作为第一响应者的视图,它将失去焦点。 - Andriy

22

使用@Dalmazio的答案在一个模仿等效UIView方法的Swift 4类别中,结果如下:

extension NSView {

    func bringSubviewToFront(_ view: NSView) {
            var theView = view
            self.sortSubviews({(viewA,viewB,rawPointer) in
                let view = rawPointer?.load(as: NSView.self)

                switch view {
                case viewA:
                    return ComparisonResult.orderedDescending
                case viewB:
                    return ComparisonResult.orderedAscending
                default:
                    return ComparisonResult.orderedSame
                }
            }, context: &theView)
    }

}

因此,要将subView带到containerView的最前面

containerView.bringSubviewToFront(subView)

与移除和重新添加视图的解决方案不同,这种方法会保持约束条件不变。


17

另一种方法是使用NSView的sortSubviewsUsingFunction:context:方法对兄弟视图集合进行重新排序。例如,定义你的比较函数:

static NSComparisonResult myCustomViewAboveSiblingViewsComparator( NSView * view1, NSView * view2, void * context )
{    
    if ([view1 isKindOfClass:[MyCustomView class]])    
        return NSOrderedDescending;    
    else if ([view2 isKindOfClass:[MyCustomView class]])    
        return NSOrderedAscending;    

    return NSOrderedSame;
}

如果您想确保自定义视图始终位于其他同级视图之上,则向自定义视图的父视图发送此消息:

[[myCustomView superview] sortSubviewsUsingFunction:myCustomViewAboveSiblingViewsComparator context:NULL];

或者,您可以将此代码移动到父视图本身,并向 self 发送消息 sortSubviewsUsingFunction:context:


2
@Jonathan。 这是针对 iOS 框架中的 UIView 的。问题是关于用于 OS X 应用程序的 NSView。 - Mathias
这个方法可以工作,但是似乎会使得其它子视图的顺序变得随机,因为文档中说“如果它们的顺序不重要,则返回NSOrderedSame”。一个保留其它视图顺序的解决方案会更好。此外,在这里使用IsKindOfClass:有些奇怪;问题并没有说明是否使用了自定义视图子类,或者需要重新排序的兄弟子视图是否属于不同的类。 - bhaller

3

我能够使这项工作正常运行,而无需调用removeFromSuperView函数。

// pop to top
[self addSubview:viewToBeMadeForemost positioned:NSWindowAbove relativeTo:nil];

这会创建一个新视图而不是使用现有的视图吗? - Krishnabhadra
2
这导致应用程序永久挂起。 - Jun
如果这个可以工作,那只是偶然;文档并不保证添加已经添加的视图的行为。 - bhaller

1
你可以通过再次添加视图来实现这一点;它不会创建另一个实例。
[self addSubview:viewToBeMadeForemost];

你可以记录这行代码执行前后的子视图数量。

1
如果这个能行,那只是意外;文档对已经添加视图的添加行为不做任何保证。 - bhaller

0

给出的代码应该可以正常工作。

    NSMutableArray *subvies = [NSMutableArray arrayWithArray:[self subviews]];//Get all subviews..

    [viewToBeMadeForemost retain]; //Retain the view to be made top view..

    [subvies removeObject:viewToBeMadeForemost];//remove it from array

    [subvies addObject:viewToBeMadeForemost];//add as last item

    [self setSubviews:subvies];//set the new array..

这应该是被接受的答案,因为它比其他答案更接近正确。但是请注意,在最后应该执行 [viewToBeMadeForemost release];,否则会有泄漏问题。 - bhaller

-11
你可以尝试这个:
[viewToBeMadeFirst.window makeKeyAndOrderFront:nil];

是的,这个对我有效:[self.window makeKeyAndOrderFront:self]; - Jiulong Zhao
10
我们正在谈论NSView,而不是NSWindow。 - IluTov

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