iOS - 获取当前显示的视图控制器及思路

在实际的项目开发中,想要获取当前正在显示的视图控制器很简单,那就是在当前的 UIViewController 类中,直接使用 self 来获得。

可是除此之外呢?比如一些和 viewController 绑定的内部 view,在不方便传递 self ,或者根本不想传递的时候,该怎么办呢?如何在这些 view 的内部里面直接获取到与之关联的 viewController?

关于这个问题,在网上已经有很多答案了,多是采用 view 的 next 属性来实现的,next 是返回下一个响应者。所以实现的思路是通过遍历,一直获取到这个 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 中,想要获取当前的视图控制器怎么办?或者你定义了一个弹出框,想要在任意地方弹出怎么办?这时候你会发现,有点难,因为有很多与该视图控制器关联的东西都中断了,你很难拿到当前正在显示的 viewController 了。

但万幸的是还有一个起点,那就是 keyWindow

UIApplication.shared.keyWindow?.rootViewController

分析

首先,视图控制器想要显示,第一个呈现出来的一定是 rootViewController

由此可以根据它的 childViewControllerspushpresent 等操作来找到它最后一个被呈现出来的视图控制器,也就是当前正在显示的视图控制器。

这里的 rootViewController 一般分为三种情况:

  • 导航栏控制器;
  • 标签栏控制器;
  • 没有导航栏或标签栏的普通视图控制器。

于是解决问题的思路就是,通过从这个最底层 rootViewController 开始,循环来逐步获取到显示在最上层的视图控制器。

后面再来解析,我直接上代码:

var topViewController: UIViewController? {
    var currentVC = UIApplication.shared.keyWindow?.rootViewController
    while true {    // 通过循环来逐步获取到显示在最上层的视图控制器
        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
}

解析

先不去管前面的代码,假设 rootVC 是导航栏控制器的情况:

// 第一个被呈现出来的控制器
let currentVC = UIApplication.shared.keyWindow?.rootViewController

// rootVC 是一个导航栏控制器
if let nav = currentVC as? UINavigationController {
    let topVC = nav.topViewController
}

想要拿到导航栏最上层的视图控制器很简单,只需要:

nav.topViewController

但是这样就完了吗?topVC 还有没有可能呈现出别的视图控制器呢?有一种可能,那就是 topVC 执行了 present。

如此 topVC 就不是最后一个被呈现出来的视图控制器了,我们要判断它执行过 present 的可能性,怎么做呢?

当然也是可以判断的,比如:

if let vc = topVC.presentedViewController {
    // 来到这里证明 topVC 执行过 present.
}

但是你以为这个的 vc 就是最后一个 presentVC 了吗?假设 vc 又执行了 present,而且 present 的是一个导航栏控制器,你该如何判断?一边要判断 vc 是不是导航栏控制器,一边又要判断这个 vc 是不是还有 presentedViewController ,是不是感觉点像死循环?

那有没有一种办法来获得最后一个被 present 出来的视图控制器呢?答案是有的,但是要通过循环来获得,类似于 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 最终会返回一个当前显示在最上层的视图控制器,也就是 currentVC ,所以我们要在这个变量的基础上进行改变,只要操作这个变量就行了,在这里先把第一次获取到的视图控制器赋值给它,也就是 nav.visibleViewController

来考虑 currentVC 是导航栏控制器的情况,如果是导航栏控制器,它的 visibleViewController 属性赋值给 currentVC ,使得以再次进入循环进行操作。因为每一次的判断条件都是基于 currentVC 这个变量来触发的。也就是说,每循环一次,currentVC 的值都会发生变化。然后继续下一轮循环,直到没有了 visibleViewController 为止。

如果循环的内部条件不在是 currentVC as? UINavigationController ,那么就只剩下普通的 UIViewController 了,判断它是否是最后一个被呈现出来的视图:

if let presentedVC = currentVC?.presentedViewController {
    currentVC = presentedVC
}

直接把它的 presentedViewController 赋值给 currentVC ,然后开始下一个循环,直到 currentVC?.presentedViewController 也为 nil 为止(循环条件不再触发)。如果期间这个 presentVC 是导航栏控制器的话,又会进入到第一个判断尽量进行处理,然后又开始下一轮循环,如此反复,直到判断条件都不在生效。于是就 break ,退出循环。

那么此时的 currentVC 就是最后一个被 present 出来的视图控制器了。

两种情况都考虑到了,不管它再如何嵌套,这个循环始终能拿到最后被 present 出来的控制器。

不知道你被绕晕了没有,如果没有那就恭喜了。理解了这两个判断后,再加上 UITabBrController 也就不难理解了:

while true {    // 通过循环来逐步获取到显示在最上层的视图控制器
    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 的判断,只需要拿到它当前展示出来的子控制器 ( selectedViewController ) 来作为判断循环条件的依据就行,如果这个 selectedViewController 是导航栏控制器或是普通的视图控制器,那么在就会在下一次循环中做处理,一直反复,直到三个条件都不再触发为止,于是就直接退出循环 ( break ) 。

如果一开始进入循环的时候所有条件都不触发,就直接 break ,然后返回:

return currentVC

无论有没有取到循环条件里面的值,最后都返回这个 currentVC

你可能感兴趣的:(iOS - 获取当前显示的视图控制器及思路)