Как найти самый верхний контроллер вида на iOS - PullRequest
236 голосов
/ 26 мая 2011

Я столкнулся с парой случаев, когда было бы удобно найти "самый верхний" контроллер представления (тот, который отвечает за текущее представление), но не нашел способа сделать это.

В основном проблема заключается в следующем: учитывая, что один выполняется в классе, который не является контроллером представления (или представление) [и не имеет адреса активного вида] и не был передан адрес самого верхнего контроллера вида (или, скажем, адрес контроллера навигации), возможно ли найти этот контроллер вида?(И если да, то как?)

Или, если это не удастся, можно ли найти самый верхний вид?

Ответы [ 40 ]

407 голосов
/ 02 октября 2012

Я думаю, вам нужна комбинация принятого ответа и @ fishstix's

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

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

    return topController;
}

Swift 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
}
151 голосов
/ 10 июля 2013

Чтобы завершить ответ 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;
    }
}
80 голосов
/ 26 мая 2011

iOS 4 представила свойство rootViewController в UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

Однако вам придется установить его самостоятельно после создания контроллера представления.

45 голосов
/ 09 сентября 2016

Полная нерекурсивная версия, учитывающая различные сценарии:

  • Контроллер представления представляет другой вид
  • Контроллер вида является 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;
     }
 }

Swift 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
    }
}
27 голосов
/ 11 ноября 2014

Получение самого популярного контроллера представления для 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()
24 голосов
/ 11 декабря 2013

Чтобы завершить ответ Эрика (который исключил всплывающие окна, контроллеры навигации, контроллеры табуляции, контроллеры представления, добавленные в качестве подпредставлений для некоторых других контроллеров представления при обходе), вот моя версия возврата текущего видимого контроллера представления:

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

- (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];
20 голосов
/ 18 июня 2014

Этот ответ включает 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;
}
13 голосов
/ 01 июня 2013

Недавно я столкнулся с такой ситуацией в одном из моих проектов, в котором требовалось отображать представление уведомлений независимо от того, какой контроллер отображался, и какого бы типа он ни был (UINavigationController, классический контроллер или пользовательский контроллер представления), когда состояние сети изменилось.

Итак, я просто выпустил свой код, который довольно прост и фактически основан на протоколе, так что он гибок с каждым типом контроллера контейнера. Кажется, это связано с последними ответами, но гораздо более гибко.

Вы можете получить код здесь: PPTopMostController

И получил самый верхний контроллер, используя

UIViewController *c = [UIViewController topMostController];
11 голосов
/ 08 мая 2013

Это улучшение ответа Эрика:

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должен быть возвращен!

8 голосов
/ 24 февраля 2015

Простое расширение для UIApplication в Swift:

Примечание:

Это заботится о moreNavigationController в пределах UITabBarController

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