获取最顶层的 UIViewController

251

我似乎无法在没有访问UINavigationController的情况下获得最顶层的UIViewController。以下是我的代码:

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

然而,它似乎没有做任何事情。 keyWindowrootViewController 似乎也是非空值,因此可选链不应该是一个问题。

注意: 像这样做是一个坏主意。 它会破坏MVC模式。


这里有一个可用的替代方案 https://dev59.com/moTba4cB1Zd3GeqP41pp#39994115 - iDevAmit
29个回答

391

presentViewController 显示一个视图控制器。它不返回视图控制器。如果您没有使用 UINavigationController,那么您可能正在寻找 presentedViewController 并且您需要从根开始迭代遍历所呈现的视图。


if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

对于Swift 3+:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

适用于 iOS 13 及以上版本

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

if var topController = keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

// topController should now be your topmost view controller
}

3
有人能解释一下while循环吗?在我看来,它好像没有可循环的内容;我甚至不确定为什么这个代码可以编译。 - Professor Tom
21
只要topController.presentedViewController返回了任何东西(即控制器有一个呈现的子控制器),循环就会继续。使用while let是为了强制确保topController.presentedViewController必须返回内容。如果它返回nil(即此控制器没有呈现子控制器),则循环将停止。在循环体中,它将子控制器重新分配为当前的topController,并再次进行循环,向下遍历视图控制器层次结构。它可以重新分配topController,因为它是外部if语句中的var - rickerbh
1
谢谢。我在网上找不到任何while let的例子。当然,可以找到很多if let的例子。 - Professor Tom
1
let x = somethingThatCouldBeNil 语法是一个非常方便的技巧,可以在任何需要真值/条件的地方使用。如果我们没有在这里使用它,我们就必须显式地赋值,然后测试看它是否真的存在。我认为它非常简洁和表达力强。 - rickerbh
2
我还记得在 WPF 时代,我不得不遍历可视树来做类似的事情,所以它并没有人们想象中那么糟糕。此外,我更喜欢这个答案,因为它使用了循环而不是递归函数,因为递归函数可能需要一个深度调用堆栈,更多的内存,并且效率较低。感谢您确认这是正确的方法。 - Tom GODDARD
显示剩余4条评论

307

拥有这个扩展

Swift 2.*

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(presented)
        }
        return controller
    }
}

Swift 3

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(controller: presented)
        }
        return controller
    }
}

你可以随后在控制器的任何位置使用这个。

if let topController = UIApplication.topViewController() {
    
}

5
我试图对这个答案进行重要的编辑,但是它被拒绝了(我不知道为什么,给出的模板原因没有意义):在递归调用之前检查nav.visibleViewController是否为空(就像检查tab.selectedViewController一样)非常重要,否则,如果它为空,您将会进入一个递归无限循环。 - Ethan G
1
根据我的理解,如果nav.visibleViewController为nil,该函数将返回nil(跳到最后一个“return”)。它怎么会陷入无限循环呢? - Desmond DAI
3
我认为将此作为UIViewController的静态函数更为合理。 - Leszek Zarna
前三个返回值可能返回nil值。为了确保不会发生这种情况,一个选项是在这些返回语句的末尾添加“?? controller”。例如,在第一个返回语句中,返回如下内容:return topViewController(controller: navigationController.visibleViewController) ?? controller - Oriol
1
如果您想捕获 UITabBarController 上以模态方式呈现的视图控制器,那么 'presentedViewController' 检查应该放在第一位。 - Tokuriku
@Tokuriku,你说得对。目前这个实现无法正确返回控制器,如果一个视图控制器在UINavigationControllerUITabBarController上以模态方式呈现。我也想知道如果一个UIAlertController被呈现会发生什么... - Sander Saelmans

92

获取顶层视图控制器的方法(适用于Swift 4/5+)

// MARK: UIApplication extensions

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)

        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)

        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

如何使用

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}

1
将参数更改为:base: UIViewController? = UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController - Joshua

21
extension UIWindow {

    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        if let navigationController = vc as? UINavigationController,
            let visibleController = navigationController.visibleViewController  {
            return UIWindow.getVisibleViewControllerFrom( vc: visibleController )
        } else if let tabBarController = vc as? UITabBarController,
            let selectedTabController = tabBarController.selectedViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: selectedTabController )
        } else {
            if let presentedViewController = vc.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
            } else {
                return vc
            }
        }
    }
}

使用方法:

if let topController = window.visibleViewController() {
    println(topController)
}

这个解决方案看起来非常有前途,但是当我尝试运行它以获取推送通知时所在的视图控制器时,它在 return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!) 上抛出了一个空值错误。 - Mike
@Mike,你应该只使用presentedViewController,而不是presentingViewController。presentedViewController才是正确的用法。 - allaire
如果您在一个模态视图控制器的顶部呈现了另一个模态视图控制器,那么您是否需要使用 .presentedViewController.presentedViewController 呢? - Baran Emre

17

我使用以下代码来获取topViewController -

它向后兼容旧的iOS版本,并且也考虑了UIScene

extension UIApplication {
    func topViewController() -> UIViewController? {
        var topViewController: UIViewController? = nil
        if #available(iOS 13, *) {
            for scene in connectedScenes {
                if let windowScene = scene as? UIWindowScene {
                    for window in windowScene.windows {
                        if window.isKeyWindow {
                            topViewController = window.rootViewController
                        }
                    }
                }
            }
        } else {
            topViewController = keyWindow?.rootViewController
        }
        while true {
            if let presented = topViewController?.presentedViewController {
                topViewController = presented
            } else if let navController = topViewController as? UINavigationController {
                topViewController = navController.topViewController
            } else if let tabBarController = topViewController as? UITabBarController {
                topViewController = tabBarController.selectedViewController
            } else {
                // Handle any other third party container in `else if` if required
                break
            }
        }
        return topViewController
    }
}

它可以这样使用:

let topController = UIApplication.shared.topViewController()
topController?.present(controllerToPresent, animated: true, completion: nil)

点赞!即使在使用C/Swift绑定的SDL2时,其他答案都无法正常工作,但这确实有效。太棒了! - FonzTech
我很高兴它有所帮助。 - atulkhatri
为什么这行需要“while true {”? - Hardik Thakkar
@HardikThakkar 它确保我们递归地检查 topViewController 的父级,直到达到没有更多父级的状态。 - atulkhatri

8

iOS13+ //最顶部的视图控制器

extension UIViewController {
    func topMostViewController() -> UIViewController {
        if self.presentedViewController == nil {
            return self
        }
        if let navigation = self.presentedViewController as? UINavigationController {
            return navigation.visibleViewController!.topMostViewController()
        }
        if let tab = self.presentedViewController as? UITabBarController {
            if let selectedTab = tab.selectedViewController {
                return selectedTab.topMostViewController()
            }
            return tab.topMostViewController()
        }
        return self.presentedViewController!.topMostViewController()
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return UIWindow.key!.rootViewController?.topMostViewController()
    }
}

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

//use let vc = UIApplication.shared.topMostViewController()

// End top Most view Controller

7

我喜欢@dianz的回答,所以这里是它的Swift 3版本。基本上是一样的,但他缺少了一个花括号,并且一些语法/变量/方法名称已更改。所以在这里!

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

使用方法仍然完全相同:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}

6

根据Dianz的回答,这是Objective-C版本

- (UIViewController *) topViewController {
   UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
   if ([baseVC isKindOfClass:[UINavigationController class]]) {
       return ((UINavigationController *)baseVC).visibleViewController;
   }

   if ([baseVC isKindOfClass:[UITabBarController class]]) {
       UIViewController *selectedTVC = ((UITabBarController*)baseVC).selectedViewController;
       if (selectedTVC) {
           return selectedTVC;
       }
   }

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}

在UITabBarController中无法工作于UINavigationController,它会返回UINavigationController,应该返回导航堆栈中的顶部控制器。 - Mike.R
谢谢,谢谢,谢谢兄弟。 - reza_khalafi

6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 我对该网站的答案和评论进行了一些测试。以下内容对我有效。

extension UIViewController {
    func topMostViewController() -> UIViewController {

        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }

        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }

        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }

        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

然后,通过以下方式获取顶层视图控制器:

UIApplication.shared.topMostViewController()

6
使用此代码查找最顶层的UIViewController:
func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}

4
这与rickerbh的回答有何不同? - ElectroBuddha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接