iOS - 獲取當(dāng)前顯示的視圖控制器及思路

在實際的項目開發(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 ,直到 nextUIViewController 為止:

/// 找到父視圖控制器并返回
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、 pushpresent 等操作來找到它最后一個被呈現(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
    }
}

我們加上了 currentVCUITabBarController 的判斷,只需要拿到它當(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容