在实际的项目开发中,想要获取当前正在显示的视图控制器很简单,那就是在当前的 UIViewController
类中,直接使用 self
来获得。
可是除此之外呢?比如一些和 viewController 绑定的内部 view,在不方便传递 self
,或者根本不想传递的时候,该怎么办呢?如何在这些 view 的内部里面直接获取到与之关联的 viewController?
关于这个问题,在网上已经有很多答案了,多是采用 view 的 next
属性来实现的,next
是返回下一个响应者。所以实现的思路是通过遍历,一直获取到这个 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 中,想要获取当前的视图控制器怎么办?或者你定义了一个弹出框,想要在任意地方弹出怎么办?这时候你会发现,有点难,因为有很多与该视图控制器关联的东西都中断了,你很难拿到当前正在显示的 viewController 了。
但万幸的是还有一个起点,那就是 keyWindow
:
UIApplication.shared.keyWindow?.rootViewController
分析
首先,视图控制器想要显示,第一个呈现出来的一定是 rootViewController
。
由此可以根据它的 childViewControllers
、 push
或 present
等操作来找到它最后一个被呈现出来的视图控制器,也就是当前正在显示的视图控制器。
这里的 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
}
}
我们加上了 currentVC
是 UITabBarController
的判断,只需要拿到它当前展示出来的子控制器 ( selectedViewController
) 来作为判断循环条件的依据就行,如果这个 selectedViewController
是导航栏控制器或是普通的视图控制器,那么在就会在下一次循环中做处理,一直反复,直到三个条件都不再触发为止,于是就直接退出循环 ( break
) 。
如果一开始进入循环的时候所有条件都不触发,就直接 break
,然后返回:
return currentVC
无论有没有取到循环条件里面的值,最后都返回这个 currentVC
。