How to find topmost view controller on iOS

后端 未结 30 2308
遥遥无期
遥遥无期 2020-11-22 08:40

I\'ve run into a couple of cases now where it would be convenient to be able to find the \"topmost\" view controller (the one responsible for the current view), but haven\'t

相关标签:
30条回答
  • 2020-11-22 09:14

    A lot of these answers are incomplete. Although this is in Objective-C, this is the best compilation of all of them that I could put together for right now, as a non-recursive block:

    • Link to Gist, in case it gets revised: https://gist.github.com/benguild/0d149bb3caaabea2dac3d2dca58c0816

    • Code for reference/comparison:

    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;
    };
    
    0 讨论(0)
  • 2020-11-22 09:16

    Getting top most view controller for Swift using extensions

    Code:

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

    Usage:

    UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()
    
    0 讨论(0)
  • 2020-11-22 09:18

    Alternative Swift solution:

    static func topMostController() -> UIViewController {
        var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
        while (topController?.presentedViewController != nil) {
            topController = topController?.presentedViewController
        }
    
        return topController!
    }
    
    0 讨论(0)
  • 2020-11-22 09:19

    To complete Eric's answer (who left out popovers, navigation controllers, tabbarcontrollers, view controllers added as subviews to some other view controllers while traversing), here is my version of returning the currently visible view controller:

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

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

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

    And now all you need to do to get top most view controller is call the above method as follows:

    UIViewController *topMostViewControllerObj = [self topViewController];
    
    0 讨论(0)
  • 2020-11-22 09:19

    Here is my take on this. Thanks to @Stakenborg for pointing out the way to skip getting UIAlertView as the top most controller

    -(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;
    }
    
    0 讨论(0)
  • 2020-11-22 09:19

    This solution is the most complete. It takes in consideration: UINavigationController UIPageViewController UITabBarController And the topmost presented view controller from the top view controller

    The example is in Swift 3.

    There are 3 overloads

    //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
    }
    
    0 讨论(0)
提交回复
热议问题