• 获取当前屏幕显示的视图控制器


      有个需求,就是在无论任何环境下,没有任何参数,我都要获得当前屏幕所显示的视图控制器。开始尝试。

    一.

      有问题问Google,摸索阶段得到了这样的一段代码,时间大约是2016年五月:

     1 //获取当前屏幕显示的viewcontroller  
     2 - (UIViewController *)getCurrentVC  {
     3   
     4     UIViewController *result = nil;  
     5       
     6     UIWindow * window = [[UIApplication sharedApplication] keyWindow];  
     7     if (window.windowLevel != UIWindowLevelNormal)  {  
     8 
     9         NSArray *windows = [[UIApplication sharedApplication] windows];  
    10         for(UIWindow * tmpWin in windows)  {
    11   
    12             if (tmpWin.windowLevel == UIWindowLevelNormal)  {
    13   
    14                 window = tmpWin;  
    15                 break;  
    16             }  
    17         }  
    18     }  
    19       
    20     UIView *frontView = [[window subviews] objectAtIndex:0];  
    21     id nextResponder = [frontView nextResponder];  
    22       
    23     if ([nextResponder isKindOfClass:[UIViewController class]])  
    24         result = nextResponder;  
    25     else  
    26         result = window.rootViewController;  
    27       
    28     return result;  
    29 } 

      这段代码由两个部分组成。我们项目是Swift语言为主体,我就尝试用Swift重写一下这个部分的代码:

      1.获得当前显示的window:

     1 // 找到当前显示的window
     2     class func getCurrentWindow() -> UIWindow? {
     3         
     4         // 找到当前显示的UIWindow
     5         var window: UIWindow? = UIApplication.shared.keyWindow
     6         /** 
     7          window有一个属性:windowLevel
     8          当 windowLevel == UIWindowLevelNormal 的时候,表示这个window是当前屏幕正在显示的window
     9          */
    10         if window?.windowLevel != UIWindowLevelNormal {
    11             
    12             for tempWindow in UIApplication.shared.windows {
    13                 
    14                 if tempWindow.windowLevel == UIWindowLevelNormal {
    15                     
    16                     window = tempWindow
    17                     break
    18                 }
    19             }
    20         }
    21         
    22         return window
    23     }

      注释写的比较清楚了。如果你们的项目只有一个window,那么可以直接获得keyWindow就可以了;但是我建议还是判断一下,保险。

      2.获得当前的视图控制器:

     1 // MARK: 获取当前屏幕显示的viewController
     2     class func getCurrentViewController1() -> UIViewController? {
     3         
     4         // 1.声明UIViewController类型的指针
     5         var viewController: UIViewController?
     6         
     7         // 2.找到当前显示的UIWindow
     8         let window: UIWindow? = self.getCurrentWindow()
     9         
    10         // 3.获得当前显示的UIWindow展示在最上面的view
    11         let frontView = window?.subviews.first
    12         
    13         // 4.找到这个view的nextResponder
    14         let nextResponder = frontView?.next
    15 
    16         if nextResponder?.isKind(of: UIViewController.classForCoder()) == true {
    17 
    18             viewController = nextResponder as? UIViewController
    19         }
    20         else {
    21             
    22             viewController = window?.rootViewController
    23         }
    24         
    25         return viewController
    26     }

      这两个方法一起使用。

      我先说结论:不好用。始终返回的都是 window的rootViewController。问题出在哪里?经过我的一路艰苦的判断,最终确定问题出在 let frontView = window?.subviews.first 这一句上。

      我不多说,我打印了下 window?.subviews 这个数组以及 nextResponder 这个UIResponder。结果如下:

    Optional([<UITransitionView: 0x1025f6090; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x174430a60>>, <UITransitionView: 0x102585880; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x174434740>>])
    
    Optional(<UIWindow: 0x102518a30; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x174256d10>; layer = <UIWindowLayer: 0x17403d5a0>>)

      可以看出,不管是subviews中的第一个还是第二个UIView,都是 UITransitionView 这样一个类型。这个类型是window与实际显示的view之间的过渡层,它的 nextResponder 直接就是window了。也就是说,这个view根本就不是当前显示在屏幕最上面的view。这条线索随之中断。

    二.

      又继续查询,发现大家的博文写的都差不多,几乎都是上面最初的那段代码,一模一样。不过我终于还是找到这样一条博文:http://www.jianshu.com/p/cb855c0f12ca,与我遇到了几乎一样的问题;而他的解决方式就是沿着响应链继续向上寻找。而在评论区有更好的一个思路:对UIView创建分类,你给我一个view,我就还你控制这个view的第一个视图控制器:http://00red.com/blog/2015/05/23/tips-find-controller/

      我直接粘贴代码了:

     1 extension UIView {
     2     
     3     func findController() -> UIViewController! {
     4     
     5         return self.findControllerWithClass(clzz: UIViewController.self)
     6     }
     7     
     8     func findNavigator() -> UINavigationController! {
     9         
    10         return self.findControllerWithClass(clzz: UINavigationController.self)
    11     }
    12     
    13     func findControllerWithClass<T>(clzz: AnyClass) -> T? {
    14         
    15         var responder = self.next
    16         
    17         while(responder != nil) {
    18             
    19             if (responder?.isKind(of: clzz))! {
    20                 
    21                 return responder as? T
    22             }
    23             responder = responder?.next
    24         }
    25         
    26         return nil
    27     }
    28 }

      这个很明显,我们只需要拿到frontView之后,这样沿着响应链循环查找一下,应该就有结果了:

    1 viewController = frontView?.findController()

      非常抱歉,又失败了。返回值为空。

      为此,我专门打印了一下在while循环中所有的responder的值:

    1 Optional(<UIWindow: 0x102518a30; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x174256d10>; layer = <UIWindowLayer: 0x17403d5a0>>)
    2 Optional(<UIApplication: 0x1024055f0>)
    3 Optional(<App.AppDelegate: 0x17413ef00>)

      恩,三步就出了App了。也就是说,这条响应链中根本就找不到UIViewController的身影。

      应该说,这个分类的思路是正确的,但是这个方法需要一个参数,就是一个view;它本质上是找到持有这个view的视图控制器;但是开头就说了,我们没有这样的一个view,没有任何参数。所以分类很好,但不适用我们这个问题。

    三.

      好吧,看来通过window.subviews这个方法,似乎是行不通的。那么真的没有办法获得视图控制器了吗?

      其实办法还是有的。

      我们知道,一般我们的架构都是这样的:

      UIApplication -> UIWindow -> UITabBarController -> UINavigationController -> push/present出来的视图控制器

      我们要拿到的也就是第五层。什么办法呢?

      自然要请出我们的递归算法咯~

     1 /* 递归找最上面的viewController */
     2     @objc class func topViewController() -> UIViewController? {
     3         
     4         return self.topViewControllerWithRootViewController(viewController: self.getCurrentWindow()?.rootViewController)
     5     }
     6     
     7     @objc class func topViewControllerWithRootViewController(viewController :UIViewController?) -> UIViewController? {
     8         
     9         if viewController == nil {
    10             
    11             return nil
    12         }
    13         
    14         if viewController?.presentedViewController != nil {
    15             
    16             return self.topViewControllerWithRootViewController(viewController: viewController?.presentedViewController!)
    17         }
    18         else if viewController?.isKind(of: UITabBarController.self) == true {
    19             
    20             return self.topViewControllerWithRootViewController(viewController: (viewController as! UITabBarController).selectedViewController)
    21         }
    22         else if viewController?.isKind(of: UINavigationController.self) == true {
    23             
    24             return self.topViewControllerWithRootViewController(viewController: (viewController as! UINavigationController).visibleViewController)
    25         }
    26         else {
    27             
    28             return viewController
    29         }
    30     }

      从头上一路向下找,最终找到当前显示的控制器。简单粗暴,但其实处处是美感,这正是递归的魅力啊。

      最终调用 topViewController() 即可。

  • 相关阅读:
    两个有序链表的合并
    Perl学习笔记
    生信-基本概念
    生信-序列比较dp[未完成]
    PAT 1091 Acute Stroke [难][bfs]
    PAT 1038 Recover the Smallest Number[dp][难]
    PAT 1078 Hashing[一般][二次探查法]
    PAT 1122 Hamiltonian Cycle[比较一般]
    PAT 1151 LCA in a Binary Tree[难][二叉树]
    PAT 1148 Werewolf
  • 原文地址:https://www.cnblogs.com/SoulKai/p/6278373.html
Copyright © 2020-2023  润新知