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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342