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

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

一.

  有问题问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() 即可。

posted @ 2017-01-12 15:14  Soul丶凯少  阅读(3602)  评论(0编辑  收藏  举报