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

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

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


当前回答

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

链接到Gist,以防修改:https://gist.github.com/benguild/0d149bb3caaabea2dac3d2dca58c0816 供参考/比较的代码:

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

其他回答

我认为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;
    }
}

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

[UIApplication sharedApplication].keyWindow.rootViewController;

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

I am thinking that perhaps one thing is being overlooked here. Perhaps it is better to pass the parent viewController into the function that is using the viewController. If you are fishing around in the view hierarchy to find the top view controller that it is probably violating separation of the Model layer and UI layer and is a code smell. Just pointing this out, I did the same, then realized it was much simpler just to pass it in to function, by having the model operation return to the UI layer where I have a reference to the view controller.

Swift 4.2中一个简洁而全面的解决方案,考虑了UINavigationControllers, UITabBarControllers, presenting和子视图控制器:

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()

为了完成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];