在iOS应用程序中获取最顶层视图/窗口的引用

97

我正在为iOS应用程序创建可重复使用的通知框架。我希望通知视图可以添加到应用程序中其他所有内容的顶部,就像UIAlertView一样。当我初始化监听NSNotification事件并响应地添加视图的管理器时,我需要获得应用程序中最顶层视图的引用。目前我的代码如下:

_topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
这对于任何iOS应用程序都有效吗?还是有更安全/更好的方法来获取顶部视图?
10个回答

113
无论何时我想要在其他所有东西的上面显示一些叠加层,我都会直接将其添加到应用程序窗口的顶部。
[[[UIApplication sharedApplication] keyWindow] addSubview:someView]

9
仅支持竖屏方向。你需要关注旋转等内容,就像这个链接中所说的那样:https://dev59.com/t3E85IYBdhLWcg3w_I78#4960988 - hfossli
2
我的iOS-8应用程序中出现(null)(故事板问题?)有什么提示吗?谢谢!(注意:(null)viewDidLoadviewWillAppear:时间都返回。viewDidAppear:太晚了。) - Olie
当我们呈现一个视图控制器时,这是否有效?添加的子视图是否会显示在呈现的视图控制器上方? - Pratyusha Terli
这不会超过键盘,但lastObject会。 - Ser Pounce
这个框架可以在所有方向上工作,并且会覆盖键盘。https://github.com/HarrisonXi/TopmostView - Harrison Xi

47

这个问题有两个部分:顶部窗口和该窗口上的顶部视图。

所有现有的答案都忽略了顶部窗口部分。但是[[UIApplication sharedApplication] keyWindow]不能保证是顶部窗口。

  1. 顶部窗口。一个应用程序中很少会存在两个具有相同windowLevel 的窗口,因此我们可以按照windowLevel 对所有窗口进行排序并获取最顶部的窗口。

    UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
        return win1.windowLevel - win2.windowLevel;
    }] lastObject];
    
  2. 为了完整起见,展示顶部窗口的顶视图。如问题中所指出的:

  3. UIView *topView = [[topWindow subviews] lastObject];
    

5
当UIAlertViews出现时,这个解决方案对我不再起作用 - 它们似乎会留下一个空的UIWindow,因此我退回到topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject]; - Gereon
4
如果键盘弹出来了,这个方法就不起作用了。因为键盘会成为最顶部的窗口。 - entropy
@Gereon,请确保您没有持有任何UIAlertView的强引用。如果它不是一个弱引用,UIAlertOverlayWindow仍将在层次结构中被识别为关键窗口,但添加到其中的子视图将不会显示。 - beebcon
在iOS 8的情况下,topWindow值不正确。 - Mehul Thakkar

36
通常这将给您顶部视图,但无法保证它对用户可见。它可能在屏幕外面,具有0.0的alpha值,或者例如大小为0x0。
还可能是keyWindow没有子视图,所以您应该先测试一下。这可能不太常见,但并非不可能。
UIWindow是UIView的子类,因此如果您想确保通知对用户可见,可以直接将其添加到keyWindow中使用addSubview:,它将立即成为最顶层的视图。我不确定这是否是您想要做的。(根据您的问题,看起来您已经知道这一点了。)

11

实际上,您的应用程序可能会有多个UIWindow。例如,如果屏幕上有一个键盘,那么[[UIApplication sharedApplication] windows]将包含至少两个窗口(您的关键窗口和键盘窗口)。

因此,如果您希望您的视图出现在它们的顶部,那么您需要像这样做:

[[[[UIApplication sharedApplication] windows] lastObject] addSubview:view];

(假设lastObject包含具有最高windowLevel优先级的窗口)。


在iOS 8中,[[UIApplication sharedApplication] windows] lastObject]的值不正确。 - Mehul Thakkar
我开始使用这个,但有时键盘窗口存在但没有显示,然后我的视图根本不显示! - teradyl

3

我将按照标题所述的问题而不是讨论来回答。在任何给定点上,哪个视图最顶部可见?

@implementation UIView (Extra)

- (UIView *)findTopMostViewForPoint:(CGPoint)point
{
    for(int i = self.subviews.count - 1; i >= 0; i--)
    {
        UIView *subview = [self.subviews objectAtIndex:i];
        if(!subview.hidden && CGRectContainsPoint(subview.frame, point))
        {
            CGPoint pointConverted = [self convertPoint:point toView:subview];
            return [subview findTopMostViewForPoint:pointConverted];
        }
    }

    return self;
}

- (UIWindow *)topmostWindow
{
    UIWindow *topWindow = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
        return win1.windowLevel - win2.windowLevel;
    }] lastObject];
    return topWindow;
}

@end

可以直接使用任何UIWindow或UIView作为接收者。

topWindow在iOS 8的情况下给出了错误的值,请您更新您的答案以适应iOS 8。 - Mehul Thakkar
1
我没有时间去找。如果你有时间,可以自由地更新这篇文章。 - hfossli

1
如果您的应用程序仅能在竖屏方向下工作,那么这就足够了:
[[[UIApplication sharedApplication] keyWindow] addSubview:yourView]

您的视图不会显示在键盘和状态栏上方。

如果您想获取一个可以覆盖键盘或状态栏的最顶层视图,或者您希望最顶层视图能够正确地随设备旋转,请尝试使用此框架:

https://github.com/HarrisonXi/TopmostView

它支持iOS7/8/9。


1
如果您要添加加载视图(例如活动指示器视图),请确保拥有一个 UIWindow 类的对象。如果您在显示加载视图之前显示操作表,则 keyWindow 将是 UIActionSheet 而不是 UIWindow。由于操作表将消失,加载视图也将随之消失。或者这就是导致我的问题的原因。
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
if (![NSStringFromClass([keyWindow class]) isEqualToString:@"UIWindow"]) {
    // find uiwindow in windows
    NSArray *windows = [UIApplication sharedApplication].windows;
    for (UIWindow *window in windows) {
        if ([NSStringFromClass([window class]) isEqualToString:@"UIWindow"]) {
            keyWindow = window;
            break;
        }
    }
}

0

试一下这个

UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];

关于窗口属性:“此属性包含一个NSWindow对象数组,对应于应用程序中当前存在的所有窗口。该数组包括所有屏幕上和离屏的窗口,无论它们是否在任何空间中可见。不能保证数组中窗口的顺序。” - Steven Fisher

0
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
if (![NSStringFromClass([keyWindow class]) isEqualToString:@"UIWindow"]) {

    NSArray *windows = [UIApplication sharedApplication].windows;
    for (UIWindow *window in windows) {
        if ([NSStringFromClass([window class]) isEqualToString:@"UIWindow"]) {
            keyWindow = window;
            break;
        }
    }
}

0

如果你想在屏幕上方添加一个视图,只需使用此代码即可。

[[UIApplication sharedApplication].keyWindow addSubView: yourView];

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