如何在iOS上找到最顶层的视图控制器

我遇到了一些情况,现在,它将是方便的,能够找到“最顶层”的视图控制器(一个负责当前视图),但还没有找到一种方法。

基本上,挑战是这样的:给定一个是在一个不是视图控制器的类中执行(或一个视图)[并且没有活动视图的地址],并且没有传递最顶层视图控制器的地址(或者,说,导航控制器的地址),是否有可能找到那个视图控制器?(如果是的话,是怎么做到的?)

或者,如果找不到,有没有可能找到最高处的风景?

237460 次浏览

iOS 4在UIWindow上引入了rootViewController属性:

[UIApplication sharedApplication].keyWindow.rootViewController;

你需要在创建视图控制器后自己设置。

@implementation UIWindow (Extensions)


- (UIViewController*) topMostController
{
UIViewController *topController = [self rootViewController];


while (topController.presentedViewController) {
topController = topController.presentedViewController;
}


return topController;
}


@end

我认为你需要一个公认的答案和@fishstix的组合

+ (UIViewController*) topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;


while (topController.presentedViewController) {
topController = topController.presentedViewController;
}


return topController;
}

斯威夫特3.0 +

func topMostController() -> UIViewController? {
guard let window = UIApplication.shared.keyWindow, let rootViewController = window.rootViewController else {
return nil
}


var topController = rootViewController


while let newTopController = topController.presentedViewController {
topController = newTopController
}


return topController
}

这是对Eric的回答的改进:

UIViewController *_topMostController(UIViewController *cont) {
UIViewController *topController = cont;


while (topController.presentedViewController) {
topController = topController.presentedViewController;
}


if ([topController isKindOfClass:[UINavigationController class]]) {
UIViewController *visible = ((UINavigationController *)topController).visibleViewController;
if (visible) {
topController = visible;
}
}


return (topController != cont ? topController : nil);
}


UIViewController *topMostController() {
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;


UIViewController *next = nil;


while ((next = _topMostController(topController)) != nil) {
topController = next;
}


return topController;
}

_topMostController(UIViewController *cont)是一个辅助函数。

现在你所需要做的就是调用topMostController(),最顶层的UIViewController应该会返回!

我最近在我的一个项目中遇到了这种情况,当网络状态发生变化时,需要显示一个通知视图,无论显示的控制器是什么,无论类型是什么(UINavigationController,经典控制器或自定义视图控制器)。

所以我刚刚发布了我的代码,这是非常简单的,实际上是基于一个协议,所以它是灵活的每一种类型的容器控制器。 它似乎与上一个答案有关,但以一种更灵活的方式

你可以在这里获取代码:PPTopMostController

并得到最顶级的控制器使用

UIViewController *c = [UIViewController topMostController];

为了完成JonasG的回答(在遍历时省略了选项卡栏控制器),这里是我返回当前可见视图控制器的版本:

- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}


- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}

这对于从任何根视图控件中找到top viewController 1非常有效

+ (UIViewController *)topViewControllerFor:(UIViewController *)viewController
{
if(!viewController.presentedViewController)
return viewController;
return [MF5AppDelegate topViewControllerFor:viewController.presentedViewController];
}


/* View Controller for Visible View */


AppDelegate *app = [UIApplication sharedApplication].delegate;
UIViewController *visibleViewController = [AppDelegate topViewControllerFor:app.window.rootViewController];

为了完成Eric的回答(在遍历时省略了弹窗、导航控制器、选项卡控制器、作为子视图添加到其他一些视图控制器的视图控制器),以下是我返回当前可见视图控制器的版本:

=====================================================================

- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}


- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)viewController {
if ([viewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)viewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navContObj = (UINavigationController*)viewController;
return [self topViewControllerWithRootViewController:navContObj.visibleViewController];
} else if (viewController.presentedViewController && !viewController.presentedViewController.isBeingDismissed) {
UIViewController* presentedViewController = viewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
}
else {
for (UIView *view in [viewController.view subviews])
{
id subViewController = [view nextResponder];
if ( subViewController && [subViewController isKindOfClass:[UIViewController class]])
{
if ([(UIViewController *)subViewController presentedViewController]  && ![subViewController presentedViewController].isBeingDismissed) {
return [self topViewControllerWithRootViewController:[(UIViewController *)subViewController presentedViewController]];
}
}
}
return viewController;
}
}

=====================================================================

现在你需要做的就是像下面这样调用上面的方法:

UIViewController *topMostViewControllerObj = [self topViewController];

如果根控制器是一个导航控制器,找到顶部可见控制器的正确方法是:

UIViewController *rootVC = [[UIApplication sharedApplication] keyWindow].rootViewController;
if ([rootVC respondsToSelector:@selector(visibleViewController)])
{
UIViewController *topVC = [(UINavigationController *)rootVC visibleViewController];
// do your thing with topVC
}

以下是UINavigationController.h的节选:

@property(nonatomic,readonly,retain) UIViewController *topViewController; // The top view controller on the stack.
@property(nonatomic,readonly,retain) UIViewController *visibleViewController; // Return modal view controller if it exists. Otherwise the top view controller.

不确定这是否会帮助你通过找到最顶层的视图控制器来实现,但我试图呈现一个新的视图控制器,但如果我的根视图控制器已经有一个模态对话框,它会被阻塞,所以我将循环到所有模态视图控制器的顶部使用以下代码:

UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController;


while( parentController.presentedViewController &&
parentController != parentController.presentedViewController )
{
parentController = parentController.presentedViewController;
}

你可以通过使用找到最顶层视图控制器

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];

以下是对我有效的方法。

我发现有时候控制器在键窗口上是nil,因为键窗口是一些操作系统的东西,如警报等。

 + (UIViewController*)topMostController
{
UIWindow *topWndow = [UIApplication sharedApplication].keyWindow;
UIViewController *topController = topWndow.rootViewController;


if (topController == nil)
{
// The windows in the array are ordered from back to front by window level; thus,
// the last window in the array is on top of all other app windows.
for (UIWindow *aWndow in [[UIApplication sharedApplication].windows reverseObjectEnumerator])
{
topController = aWndow.rootViewController;
if (topController)
break;
}
}


while (topController.presentedViewController) {
topController = topController.presentedViewController;
}


return topController;
}
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}


- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}

这个答案包括childViewControllers,并保持一个干净和可读的实现。

+ (UIViewController *)topViewController
{
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;


return [rootViewController topVisibleViewController];
}


- (UIViewController *)topVisibleViewController
{
if ([self isKindOfClass:[UITabBarController class]])
{
UITabBarController *tabBarController = (UITabBarController *)self;
return [tabBarController.selectedViewController topVisibleViewController];
}
else if ([self isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController *)self;
return [navigationController.visibleViewController topVisibleViewController];
}
else if (self.presentedViewController)
{
return [self.presentedViewController topVisibleViewController];
}
else if (self.childViewControllers.count > 0)
{
return [self.childViewControllers.lastObject topVisibleViewController];
}


return self;
}

你应该使用:

[UIApplication sharedApplication].window.rootViewController;

[UIApplication sharedApplication].keyWindow上有一个uiactionsheet时,使用这个答案中提到的keyWindow是不正确的。

扩展@Eric的回答,你需要小心,keyWindow实际上是你想要的窗口。例如,如果您试图在点击警报视图中的某些内容后使用此方法,keyWindow实际上将是警报的窗口,这无疑会给您带来问题。这发生在我在野外通过警报处理深度链接时,并导致SIGABRTs没有堆栈跟踪。要调试的婊子。

下面是我现在使用的代码:

- (UIViewController *)getTopMostViewController {
UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
if (topWindow.windowLevel != UIWindowLevelNormal) {
NSArray *windows = [UIApplication sharedApplication].windows;
for(topWindow in windows)
{
if (topWindow.windowLevel == UIWindowLevelNormal)
break;
}
}


UIViewController *topViewController = topWindow.rootViewController;


while (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
}


return topViewController;
}

你可以把这个和你喜欢的从这个问题的其他答案中检索顶视图控制器的方法混合在一起。

以下是我的看法。感谢@Stakenborg指出了跳过UIAlertView作为最顶层控制器的方法

-(UIWindow *) returnWindowWithWindowLevelNormal
{
NSArray *windows = [UIApplication sharedApplication].windows;
for(UIWindow *topWindow in windows)
{
if (topWindow.windowLevel == UIWindowLevelNormal)
return topWindow;
}
return [UIApplication sharedApplication].keyWindow;
}


-(UIViewController *) getTopMostController
{
UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
if (topWindow.windowLevel != UIWindowLevelNormal)
{
topWindow = [self returnWindowWithWindowLevelNormal];
}


UIViewController *topController = topWindow.rootViewController;
if(topController == nil)
{
topWindow = [UIApplication sharedApplication].delegate.window;
if (topWindow.windowLevel != UIWindowLevelNormal)
{
topWindow = [self returnWindowWithWindowLevelNormal];
}
topController = topWindow.rootViewController;
}


while(topController.presentedViewController)
{
topController = topController.presentedViewController;
}


if([topController isKindOfClass:[UINavigationController class]])
{
UINavigationController *nav = (UINavigationController*)topController;
topController = [nav.viewControllers lastObject];


while(topController.presentedViewController)
{
topController = topController.presentedViewController;
}
}


return topController;
}

使用扩展为Swift获得最顶部的视图控制器

代码:

extension UIViewController {
@objc func topMostViewController() -> UIViewController {
// Handling Modal views
if let presentedViewController = self.presentedViewController {
return presentedViewController.topMostViewController()
}
// Handling UIViewController's added as subviews to some other views.
else {
for view in self.view.subviews
{
// Key property which most of us are unaware of / rarely use.
if let subViewController = view.next {
if subViewController is UIViewController {
let viewController = subViewController as! UIViewController
return viewController.topMostViewController()
}
}
}
return self
}
}
}


extension UITabBarController {
override func topMostViewController() -> UIViewController {
return self.selectedViewController!.topMostViewController()
}
}


extension UINavigationController {
override func topMostViewController() -> UIViewController {
return self.visibleViewController!.topMostViewController()
}
}

用法:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()

另一个解决方案依赖于responder链,它可能工作,也可能不工作,这取决于第一个responder是什么:

  1. 获取第一个响应器
  2. 获取与第一个responder相关联的UIViewController

示例伪代码:

+ (UIViewController *)currentViewController {
UIView *firstResponder = [self firstResponder]; // from the first link above, but not guaranteed to return a UIView, so this should be handled more appropriately.
UIViewController *viewController = [firstResponder viewController]; // from the second link above
return viewController;
}

迅速:

extension UIWindow {


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


class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if vc.isKindOfClass(UINavigationController.self) {


let navigationController = vc as UINavigationController
return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController)


} else if vc.isKindOfClass(UITabBarController.self) {


let tabBarController = vc as UITabBarController
return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController!)


} else {


if let presentedViewController = vc.presentedViewController {


return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)


} else {


return vc;
}
}
}

用法:

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

简单的扩展UIApplication在Swift:

注意:

它关心UITabBarController中的moreNavigationController

extension UIApplication {


class func topViewController(baseViewController: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {


if let navigationController = baseViewController as? UINavigationController {
return topViewController(navigationController.visibleViewController)
}


if let tabBarViewController = baseViewController as? UITabBarController {


let moreNavigationController = tabBarViewController.moreNavigationController


if let topViewController = moreNavigationController.topViewController where topViewController.view.window != nil {
return topViewController(topViewController)
} else if let selectedViewController = tabBarViewController.selectedViewController {
return topViewController(selectedViewController)
}
}


if let splitViewController = baseViewController as? UISplitViewController where splitViewController.viewControllers.count == 1 {
return topViewController(splitViewController.viewControllers[0])
}


if let presentedViewController = baseViewController?.presentedViewController {
return topViewController(presentedViewController)
}


return baseViewController
}
}

简单的用法:

if let topViewController = UIApplication.topViewController() {
//do sth with top view controller
}

Swift替代解决方案:

static func topMostController() -> UIViewController {
var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
while (topController?.presentedViewController != nil) {
topController = topController?.presentedViewController
}


return topController!
}

为了避免很多复杂性,我通过在委托中创建一个viewController来跟踪当前的viewController,并在每个viewDidLoad方法中设置它为self,这样每当你加载一个新视图时,委托中持有的viewController将对应于该视图的viewController。这可能是丑陋的,但它工作得很好,没有必要有一个导航控制器或任何废话。

之前的答案似乎不能处理rootController是UITabBarController或UINavigationController的情况。

下面是swift中适用于这些情况的函数:

func getCurrentView() -> UIViewController?
{
if let window = UIApplication.sharedApplication().keyWindow, var currentView: UIViewController = window.rootViewController
{
while (currentView.presentedViewController != nil)
{
if let presented = currentView.presentedViewController
{
currentView = presented
}
}


if currentView is UITabBarController
{
if let visible = (currentView as! UITabBarController).selectedViewController
{
currentView = visible;
}
}


if currentView is UINavigationController
{
if let visible = (currentView as! UINavigationController).visibleViewController
{
currentView = visible;
}
}


return currentView
}


return nil
}

我认为Rajesh的解决方案几乎是完美的,但我认为从上到下遍历子视图更好,我改为如下:

+ (UIViewController *)topViewController:(UIViewController *)viewController{


if (viewController.presentedViewController)
{


UIViewController *presentedViewController = viewController.presentedViewController;
return [self topViewController:presentedViewController];
}
else if ([viewController isKindOfClass:[UITabBarController class]])
{


UITabBarController *tabBarController = (UITabBarController *)viewController;
return [self topViewController:tabBarController.selectedViewController];
}


else if ([viewController isKindOfClass:[UINavigationController class]])
{


UINavigationController *navController = (UINavigationController *)viewController;


return [self topViewController:navController.visibleViewController];
}


// Handling UIViewController's added as subviews to some other views.
else {


NSInteger subCount = [viewController.view subviews].count - 1;


for (NSInteger index = subCount; index >=0 ; --index)
{


UIView *view = [[viewController.view subviews] objectAtIndex:index];


id subViewController = [view nextResponder];    // Key property which most of us are unaware of / rarely use.


if ( subViewController && [subViewController isKindOfClass:[UIViewController class]])
{
return [self topViewController:subViewController];
}
}
return viewController;
}
}

伟大的解决方案在Swift,实现在AppDelegate

func getTopViewController()->UIViewController{
return topViewControllerWithRootViewController(UIApplication.sharedApplication().keyWindow!.rootViewController!)
}
func topViewControllerWithRootViewController(rootViewController:UIViewController)->UIViewController{
if rootViewController is UITabBarController{
let tabBarController = rootViewController as! UITabBarController
return topViewControllerWithRootViewController(tabBarController.selectedViewController!)
}
if rootViewController is UINavigationController{
let navBarController = rootViewController as! UINavigationController
return topViewControllerWithRootViewController(navBarController.visibleViewController)
}
if let presentedViewController = rootViewController.presentedViewController {
return topViewControllerWithRootViewController(presentedViewController)
}
return rootViewController
}

下面两个函数可以帮助在视图控制器堆栈中找到topViewController。以后可能需要自定义,但是对于这段代码来说,理解topViewController或viewcontroller堆栈的概念非常棒。

- (UIViewController*)findTopViewController {


id  topControler  = [self topMostController];


UIViewController* topViewController;
if([topControler isKindOfClass:[UINavigationController class]]) {
topViewController = [[(UINavigationController*)topControler viewControllers] lastObject];
} else if ([topControler isKindOfClass:[UITabBarController class]]) {
//Here you can get reference of top viewcontroller from stack of viewcontrollers on UITabBarController
} else {
//topController is a preented viewController
topViewController = (UIViewController*)topControler;
}
//NSLog(@"Top ViewController is: %@",NSStringFromClass([topController class]));
return topViewController;
}


- (UIViewController*)topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;


while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
//NSLog(@"Top View is: %@",NSStringFromClass([topController class]));
return topController;
}

你可以使用[viewController Class]方法找出一个viewController的类的类型。

对于最新的Swift版本:
创建一个文件,命名为UIWindowExtension.swift并粘贴以下代码片段

import UIKit


public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}


public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}


func getTopViewController() -> UIViewController? {
let appDelegate = UIApplication.shared.delegate
if let window = appDelegate!.window {
return window?.visibleViewController
}
return nil
}

可以在任何地方使用:

if let topVC = getTopViewController() {


}

这是一个Swift的应用程序的实现与UINavigationController的根。

  if let nav = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController{
//get the current's navigation view controller
var vc = nav.topViewController
while vc?.presentedViewController != nil {
vc = vc?.presentedViewController
}
return vc
}

另一个Swift解决方案

func topController() -> UIViewController? {


// recursive follow
func follow(from:UIViewController?) -> UIViewController? {
if let to = (from as? UITabBarController)?.selectedViewController {
return follow(to)
} else if let to = (from as? UINavigationController)?.visibleViewController {
return follow(to)
} else if let to = from?.presentedViewController {
return follow(to)
}
return from
}


let root = UIApplication.sharedApplication().keyWindow?.rootViewController


return follow(root)


}

另一个Swift解决方案

extension UIViewController {
static var topmostViewController: UIViewController? {
return UIApplication.sharedApplication().keyWindow?.topmostViewController
}


var topmostViewController: UIViewController? {
return presentedViewController?.topmostViewController ?? self
}
}


extension UINavigationController {
override var topmostViewController: UIViewController? {
return visibleViewController?.topmostViewController
}
}


extension UITabBarController {
override var topmostViewController: UIViewController? {
return selectedViewController?.topmostViewController
}
}


extension UIWindow {
var topmostViewController: UIViewController? {
return rootViewController?.topmostViewController
}
}

一个完整的非递归版本,照顾到不同的场景:

  • 视图控制器正在呈现另一个视图
  • 视图控制器是UINavigationController
  • 视图控制器是UITabBarController

objective - c

 UIViewController *topViewController = self.window.rootViewController;
while (true)
{
if (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
} else if ([topViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)topViewController;
topViewController = nav.topViewController;
} else if ([topViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController *tab = (UITabBarController *)topViewController;
topViewController = tab.selectedViewController;
} else {
break;
}
}

斯威夫特4 +

extension UIWindow {
func topViewController() -> UIViewController? {
var top = self.rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
return top
}
}
这个解决方案是最完整的。它考虑到: UINavigationController UIPageViewController UITabBarController 最上面的显示视图控制器来自顶层视图控制器

以Swift 3为例。

有3个重载

//Get the topmost view controller for the current application.
public func MGGetTopMostViewController() -> UIViewController?  {


if let currentWindow:UIWindow = UIApplication.shared.keyWindow {
return MGGetTopMostViewController(fromWindow: currentWindow)
}


return nil
}


//Gets the topmost view controller from a specific window.
public func MGGetTopMostViewController(fromWindow window:UIWindow) -> UIViewController? {


if let rootViewController:UIViewController = window.rootViewController
{
return MGGetTopMostViewController(fromViewController:  rootViewController)
}


return nil
}




//Gets the topmost view controller starting from a specific UIViewController
//Pass the rootViewController into this to get the apps top most view controller
public func MGGetTopMostViewController(fromViewController viewController:UIViewController) -> UIViewController {


//UINavigationController
if let navigationViewController:UINavigationController = viewController as? UINavigationController {
let viewControllers:[UIViewController] = navigationViewController.viewControllers
if navigationViewController.viewControllers.count >= 1 {
return MGGetTopMostViewController(fromViewController: viewControllers[viewControllers.count - 1])
}
}


//UIPageViewController
if let pageViewController:UIPageViewController = viewController as? UIPageViewController {
if let viewControllers:[UIViewController] = pageViewController.viewControllers {
if viewControllers.count >= 1 {
return MGGetTopMostViewController(fromViewController: viewControllers[0])
}
}
}


//UITabViewController
if let tabBarController:UITabBarController = viewController as? UITabBarController {
if let selectedViewController:UIViewController = tabBarController.selectedViewController {
return MGGetTopMostViewController(fromViewController: selectedViewController)
}
}


//Lastly, Attempt to get the topmost presented view controller
var presentedViewController:UIViewController! = viewController.presentedViewController
var nextPresentedViewController:UIViewController! = presentedViewController?.presentedViewController


//If there is a presented view controller, get the top most prensentedViewController and return it.
if presentedViewController != nil {
while nextPresentedViewController != nil {


//Set the presented view controller as the next one.
presentedViewController = nextPresentedViewController


//Attempt to get the next presented view controller
nextPresentedViewController = presentedViewController.presentedViewController
}
return presentedViewController
}


//If there is no topmost presented view controller, return the view controller itself.
return viewController
}

很多答案都是不完整的。虽然这是在Objective-C中,但这是我现在能把它们放在一起的最好的编译,作为一个非递归的块:

UIViewController *(^topmostViewControllerForFrontmostNormalLevelWindow)(void) = ^UIViewController *{
// NOTE: Adapted from various stray answers here:
//   https://stackoverflow.com/questions/6131205/iphone-how-to-find-topmost-view-controller/20515681


UIViewController *viewController;


for (UIWindow *window in UIApplication.sharedApplication.windows.reverseObjectEnumerator.allObjects) {
if (window.windowLevel == UIWindowLevelNormal) {
viewController = window.rootViewController;
break;
}
}


while (viewController != nil) {
if ([viewController isKindOfClass:[UITabBarController class]]) {
viewController = ((UITabBarController *)viewController).selectedViewController;
} else if ([viewController isKindOfClass:[UINavigationController class]]) {
viewController = ((UINavigationController *)viewController).visibleViewController;
} else if (viewController.presentedViewController != nil && !viewController.presentedViewController.isBeingDismissed) {
viewController = viewController.presentedViewController;
} else if (viewController.childViewControllers.count > 0) {
viewController = viewController.childViewControllers.lastObject;
} else {
BOOL repeat = NO;


for (UIView *view in viewController.view.subviews.reverseObjectEnumerator.allObjects) {
if ([view.nextResponder isKindOfClass:[UIViewController class]]) {
viewController = (UIViewController *)view.nextResponder;


repeat = YES;
break;
}
}


if (!repeat) {
break;
}
}
}


return viewController;
};

我认为大多数答案都完全忽略了UINavigationViewController,所以我用以下实现来处理这个用例。

+ (UIViewController *)topMostController {
UIViewController * topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController || [topController isMemberOfClass:[UINavigationController class]]) {
if([topController isMemberOfClass:[UINavigationController class]]) {
topController = [topController childViewControllers].lastObject;
} else {
topController = topController.presentedViewController;
}
}


return topController;
}

我在想也许有一件事被忽略了。也许将父viewController传递给使用viewController的函数会更好。如果你在视图层次结构中寻找顶视图控制器,这可能违反了Model层和UI层的分离,是一种代码气味。只是指出这一点,我做了同样的事情,然后意识到它是简单得多,只是把它传递给函数,通过让模型操作返回到UI层,在那里我有一个视图控制器的引用。

Swift 4.2中一个简洁而全面的解决方案,考虑了uinavigationcontrolleruitabbarcontroller提出了孩子视图控制器:

extension UIViewController {
func topmostViewController() -> UIViewController {
if let navigationVC = self as? UINavigationController,
let topVC = navigationVC.topViewController {
return topVC.topmostViewController()
}
if let tabBarVC = self as? UITabBarController,
let selectedVC = tabBarVC.selectedViewController {
return selectedVC.topmostViewController()
}
if let presentedVC = presentedViewController {
return presentedVC.topmostViewController()
}
if let childVC = children.last {
return childVC.topmostViewController()
}
return self
}
}


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

用法:

let viewController = UIApplication.shared.topmostViewController()
我的问题有点不同,我在我的应用程序中使用SWRevealViewController。 我使用了Yuchen Zhong的回答,但它总是返回topViewController作为SWRevealViewController。对于那些使用SWRevealViewController或其他一些pod来开发侧菜单的人。以下是我对钟宇晨回答的扩展:

extension UIApplication {
class func topViewController() -> UIViewController? {
var topVC = shared.keyWindow!.rootViewController
while true {
if let presented = topVC?.presentedViewController {
topVC = presented
} else if let nav = topVC as? UINavigationController {
topVC = nav.visibleViewController
} else if let tab = topVC as? UITabBarController {
topVC = tab.selectedViewController
}else if let swRVC = topVC as? SWRevealViewController {
topVC = swRVC.frontViewController
} else {
break
}
}
return topVC
}
}

Swift 4.2扩展


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
}
}

在任何地方都可以使用,

 UIApplication.topViewController()?.present(yourController, animated: true, completion: nil)

或者像,

 UIApplication.topViewController()?
.navigationController?
.popToViewController(yourController,
animated: true)

适合任何类,如UINavigationController, UITabBarController

享受吧!

使用下面的扩展来抓取当前可见的UIViewController。适用于Swift 4.0及更高版本

Swift 4.0及以上版本:

extension UIApplication {
    

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

如何使用?

let objViewcontroller = UIApplication.topViewController()

我知道已经很晚了,可能是多余的。但以下是我提出的对我有用的片段:

    static func topViewController() -> UIViewController? {
return topViewController(vc: UIApplication.shared.keyWindow?.rootViewController)
}


private static func topViewController(vc:UIViewController?) -> UIViewController? {
if let rootVC = vc {
guard let presentedVC = rootVC.presentedViewController else {
return rootVC
}
if let presentedNavVC = presentedVC as? UINavigationController {
let lastVC = presentedNavVC.viewControllers.last
return topViewController(vc: lastVC)
}
return topViewController(vc: presentedVC)
}
return nil
}

斯威夫特5

试试这个

let topVisibleVC = UIApplication.shared.keyWindow?.rootViewController?.visibleViewController