在實際的項目開發(fā)中,想要獲取當(dāng)前正在顯示的視圖控制器很簡單,那就是在當(dāng)前的 UIViewController 類中,直接使用 self 來獲得。
可是除此之外呢?比如一些和 viewController 綁定的內(nèi)部 view,在不方便傳遞 self ,或者根本不想傳遞的時候,該怎么辦呢?如何在這些 view 的內(nèi)部里面直接獲取到與之關(guān)聯(lián)的 viewController?
關(guān)于這個問題,在網(wǎng)上已經(jīng)有很多答案了,多是采用 view 的 next 屬性來實現(xiàn)的,next 是返回下一個響應(yīng)者。所以實現(xiàn)的思路是通過遍歷,一直獲取到這個 view 的 next ,直到 next 是 UIViewController 為止:
/// 找到父視圖控制器并返回
var parentViewController: UIViewController? {
weak var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
可是這樣又有新的問題,那就是,如果不是在 viewController 和 view 的類中,想要使用所謂的 " self ",該怎么辦呢?
比如在一個 struct 或者 viewModel 中,想要獲取當(dāng)前的視圖控制器怎么辦?或者你定義了一個彈出框,想要在任意地方彈出怎么辦?這時候你會發(fā)現(xiàn),有點難,因為有很多與該視圖控制器關(guān)聯(lián)的東西都中斷了,你很難拿到當(dāng)前正在顯示的 viewController 了。
但萬幸的是還有一個起點,那就是 keyWindow :
UIApplication.shared.keyWindow?.rootViewController
分析
首先,視圖控制器想要顯示,第一個呈現(xiàn)出來的一定是 rootViewController 。
由此可以根據(jù)它的 childViewControllers、 push 或 present 等操作來找到它最后一個被呈現(xiàn)出來的視圖控制器,也就是當(dāng)前正在顯示的視圖控制器。
這里的 rootViewController 一般分為三種情況:
- 導(dǎo)航欄控制器;
- 標(biāo)簽欄控制器;
- 沒有導(dǎo)航欄或標(biāo)簽欄的普通視圖控制器。
于是解決問題的思路就是,通過從這個最底層 rootViewController 開始,循環(huán)來逐步獲取到顯示在最上層的視圖控制器。
后面再來解析,我直接上代碼:
var topViewController: UIViewController? {
var currentVC = UIApplication.shared.keyWindow?.rootViewController
while true { // 通過循環(huán)來逐步獲取到顯示在最上層的視圖控制器
if let nav = currentVC as? UINavigationController {
currentVC = nav.visibleViewController
}else if let tab = currentVC as? UITabBarController {
currentVC = tab.selectedViewController
}else if let presentedVC = currentVC?.presentedViewController {
currentVC = presentedVC
}else {
break
}
}
return currentVC
}
解析
先不去管前面的代碼,假設(shè) rootVC 是導(dǎo)航欄控制器的情況:
// 第一個被呈現(xiàn)出來的控制器
let currentVC = UIApplication.shared.keyWindow?.rootViewController
// rootVC 是一個導(dǎo)航欄控制器
if let nav = currentVC as? UINavigationController {
let topVC = nav.topViewController
}
想要拿到導(dǎo)航欄最上層的視圖控制器很簡單,只需要:
nav.topViewController
但是這樣就完了嗎?topVC 還有沒有可能呈現(xiàn)出別的視圖控制器呢?有一種可能,那就是 topVC 執(zhí)行了 present。
如此 topVC 就不是最后一個被呈現(xiàn)出來的視圖控制器了,我們要判斷它執(zhí)行過 present 的可能性,怎么做呢?
當(dāng)然也是可以判斷的,比如:
if let vc = topVC.presentedViewController {
// 來到這里證明 topVC 執(zhí)行過 present.
}
但是你以為這個的 vc 就是最后一個 presentVC 了嗎?假設(shè) vc 又執(zhí)行了 present,而且 present 的是一個導(dǎo)航欄控制器,你該如何判斷?一邊要判斷 vc 是不是導(dǎo)航欄控制器,一邊又要判斷這個 vc 是不是還有 presentedViewController ,是不是感覺點像死循環(huán)?
那有沒有一種辦法來獲得最后一個被 present 出來的視圖控制器呢?答案是有的,但是要通過循環(huán)來獲得,類似于 view 的采用 next 的方式,先來看看最簡單的判斷,暫時不考慮有 UITabViewController 的情況:
while true {
if let nav = currentVC as? UINavigationController {
currentVC = nav.visibleViewController
}if let presentedVC = currentVC?.presentedViewController {
currentVC = presentedVC
}else {
break
}
}
來分析一下:
currentVC = nav.visibleViewController
這個計算型屬性 topViewController 最終會返回一個當(dāng)前顯示在最上層的視圖控制器,也就是 currentVC ,所以我們要在這個變量的基礎(chǔ)上進(jìn)行改變,只要操作這個變量就行了,在這里先把第一次獲取到的視圖控制器賦值給它,也就是 nav.visibleViewController 。
來考慮 currentVC 是導(dǎo)航欄控制器的情況,如果是導(dǎo)航欄控制器,它的 visibleViewController 屬性賦值給 currentVC ,使得以再次進(jìn)入循環(huán)進(jìn)行操作。因為每一次的判斷條件都是基于 currentVC 這個變量來觸發(fā)的。也就是說,每循環(huán)一次,currentVC 的值都會發(fā)生變化。然后繼續(xù)下一輪循環(huán),直到?jīng)]有了 visibleViewController 為止。
如果循環(huán)的內(nèi)部條件不在是 currentVC as? UINavigationController ,那么就只剩下普通的 UIViewController 了,判斷它是否是最后一個被呈現(xiàn)出來的視圖:
if let presentedVC = currentVC?.presentedViewController {
currentVC = presentedVC
}
直接把它的 presentedViewController 賦值給 currentVC ,然后開始下一個循環(huán),直到 currentVC?.presentedViewController 也為 nil 為止(循環(huán)條件不再觸發(fā))。如果期間這個 presentVC 是導(dǎo)航欄控制器的話,又會進(jìn)入到第一個判斷盡量進(jìn)行處理,然后又開始下一輪循環(huán),如此反復(fù),直到判斷條件都不在生效。于是就 break ,退出循環(huán)。
那么此時的 currentVC 就是最后一個被 present 出來的視圖控制器了。
兩種情況都考慮到了,不管它再如何嵌套,這個循環(huán)始終能拿到最后被 present 出來的控制器。
不知道你被繞暈了沒有,如果沒有那就恭喜了。理解了這兩個判斷后,再加上 UITabBrController 也就不難理解了:
while true { // 通過循環(huán)來逐步獲取到顯示在最上層的視圖控制器
if let nav = currentVC as? UINavigationController {
currentVC = nav.visibleViewController
}else if let tab = currentVC as? UITabBarController {
currentVC = tab.selectedViewController
}else if let presentedVC = currentVC?.presentedViewController {
currentVC = presentedVC
}else {
break
}
}
我們加上了 currentVC 是 UITabBarController 的判斷,只需要拿到它當(dāng)前展示出來的子控制器 ( selectedViewController ) 來作為判斷循環(huán)條件的依據(jù)就行,如果這個 selectedViewController 是導(dǎo)航欄控制器或是普通的視圖控制器,那么在就會在下一次循環(huán)中做處理,一直反復(fù),直到三個條件都不再觸發(fā)為止,于是就直接退出循環(huán) ( break ) 。
如果一開始進(jìn)入循環(huán)的時候所有條件都不觸發(fā),就直接 break ,然后返回:
return currentVC
無論有沒有取到循環(huán)條件里面的值,最后都返回這個 currentVC。