獲取當前屏幕顯示的視圖控制器


  有個需求,就是在無論任何環境下,沒有任何參數,我都要獲得當前屏幕所顯示的視圖控制器。開始嘗試。

一.

  有問題問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() 即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM