美文网首页IOS开发WindowiOS Development
获取 APP 屏幕最上层的 View Controller

获取 APP 屏幕最上层的 View Controller

作者: waylen | 来源:发表于2015-12-30 18:10 被阅读4088次

    有一个 APP 里有若干个 View Controller,有些 Controller 里的 View 涉及到用户敏感信息,比如显示用户存款金额和社保号。程序进入后台后,我需要在这类 Controller 上 present 一个模态的 View Controller 进行遮挡(典型的用法就是弹出一个手势密码,或者对这个 Controller 上的 View 进行高斯模糊),但是在其他不涉及用户隐私的 Controller 上,我不能这样做。那么在程序进入后台时,我如何知道屏幕最上层的一个Controller 是什么鬼(拿到这个 Controller 的实例),并选择性的将其遮挡?

    我去百度了这个问题,结果有不少答案在列。但是所有答案全是同一段类似的代码:

    //获取当前屏幕显示的 View Controller
    - (UIViewController *)getCurrentVC
    {
        UIViewController *result = nil;
        UIWindow * window        = [[UIApplication sharedApplication] keyWindow];
        if (window.windowLevel != UIWindowLevelNormal) {
            NSArray *windows = [[UIApplication sharedApplication] windows];
            for(UIWindow * tmpWin in windows) {
                if (tmpWin.windowLevel == UIWindowLevelNormal) {
                    window = tmpWin;
                    break;
                }
            }
        }
        
        UIView *frontView = [[window subviews] objectAtIndex:0];
        id nextResponder  = [frontView nextResponder];
        
        if ([nextResponder isKindOfClass:[UIViewController class]])
            result = nextResponder;
        else
            result = window.rootViewController;
        
        return result;
    }
    

    分析了一下这段源码,前半部分是获取 APP 的普通层级 window,我们的视图一般会显示在这个 window 上。后部分源码就有点不靠谱了,断点打印[window subviews]的值发现这个数组里只有一个元素:

    屏幕快照 2015-12-30 下午5.37.04.png

    这个UILayoutContainerView就是 window 上的第一个 subview,那么他的 nextResponder 自然是这个 window ,所以结果总是返回rootViewController。而 rootViewController 可能是 TabBarController 也可能是 NavigationController,假设我们的目标控制器有着下面这种层级关系:

    Window -> TabBarController -> NavigationController -> ViewController1 -> Target ViewController

    上面这种方法只能拿到 TabBarController

    那么我们换一种思路。若要获取最上层的 view controller 可以采取【顺藤摸瓜】模式,从 root view controller 开始递归遍历所有的控制器,如果找到的控制器是 UITabBarController 的子类,就找它的selectedViewController继续向递归,如果找到的控制器是 UINavigationController 的子类,就找它的visibleViewController继续递归。这样一路找上来总会到头,最头上的控制器就是我们的目标控制器。

    这里我将其写成一个UIApplication的分类方便使用:

    @implementation UIApplication (ActivityViewController)
    
    - (UIViewController *)activityViewController {
        __block UIWindow *normalWindow = [self.delegate window];
        if (normalWindow.windowLevel != UIWindowLevelNormal) {
            [self.windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                if (obj.windowLevel == UIWindowLevelNormal) {
                    normalWindow = obj;
                    *stop        = YES;
                }
            }];
        }
        
        return [self p_nextTopForViewController:normalWindow.rootViewController];
    }
    
    - (UIViewController *)p_nextTopForViewController:(UIViewController *)inViewController {
        while (inViewController.presentedViewController) {
            inViewController = inViewController.presentedViewController;
        }
        
        if ([inViewController isKindOfClass:[UITabBarController class]]) {
            UIViewController *selectedVC = [self p_nextTopForViewController:((UITabBarController *)inViewController).selectedViewController];
            return selectedVC;
        } else if ([inViewController isKindOfClass:[UINavigationController class]]) {
            UIViewController *selectedVC = [self p_nextTopForViewController:((UINavigationController *)inViewController).visibleViewController];
            return selectedVC;
        } else {
            return inViewController;
        }
    }
    
    @end
    

    在需要的时候直接使用[[UIApplication sharedApplication] activityViewController]就可以返回最上层的 view controller 了:)。

    2017-02-06 更新

    该方法太过繁琐,可参考另一种方案

    相关文章

      网友评论

      • PGOne爱吃饺子:楼主,你的这个需求是什么样的,没有明白你的具体需求。
      • Maple_heather:楼主考虑过使用 Swizzing hook viewDidAppear 来获取最上层的视图没有,虽然也不支持 addChild
        waylen:@Maple_heather 嗯,项目地址:https://github.com/zhwayne/TopVC,有可能的话,希望能帮我改进,谢谢。
        Maple_heather:@waylen 博主你好,如果使用 swizzing 成功了,可以给我发一个 demo 吗?2206618839@qq.com
        waylen:@Maple_heather swizzing 方案需要自行维护 viewcontroller stack,目前正考虑使用这一方案替代。
      • 天下无贼:说的很有道理 网上很多一模一样的代码 到目前为止还没有找到一个真正通用的 :flushed: :flushed:
      • 6097f97cae80:最好是自己管理一个栈,虽然麻烦点,但保证不会出错
      • dff31b46c8d8:您好,我想请问一下,为什么我调用这个分类的方法后获取到的控制器还是根视图控制器呢
        waylen:@强子0001 那这个分类确实对你的 APP 层级不起作用,目前它只支持 UINavigationController 和 UITabBarController 的各种组合,如果是在 UIViewController 上 addChil ViewController,它就有点无能为力了。或许你可以尝试进一步完善这个分类 。
        dff31b46c8d8:@waylen 整体结构是一个抽屉效果,是RESideMenu这个侧滑的三方库文件,拿到的总是RESideMenu这个根
        waylen:@强子0001 你的 APP 的结构层级是什么样子的?
      • 耿杰:你没有试过moda出来控制器 :flushed:

      本文标题:获取 APP 屏幕最上层的 View Controller

      本文链接:https://www.haomeiwen.com/subject/xlmihttx.html