Как найти самый верхний контроллер вида на iOS

253

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

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

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

Горячие лижет
источник
Итак, вы говорите, что это невозможно.
Hot Licks
@ Даниель нет, я говорю, что кажется, что ваш код может использовать некоторую редизайн, потому что вам редко нужно это знать. Кроме того, идея «верхнего уровня» действительна только в определенных контекстах, и даже тогда не всегда.
Дейв Делонг
@ Даниил Я неправильно понял твой вопрос. Есть много вопросов и ответов, которые пытаются ответить на этот вопрос. Это зависит от вашего потока контроллеров представления. @ Ответ Уилбура должен послужить хорошей отправной точкой для его отслеживания.
Дипак Дандупролу
Что ж, давайте упростим это для конкретного случая. Если бы я хотел написать клон UIAlertView, как бы я это сделал? Обратите внимание, что он может нормально работать, не передавая никакой адресации другим контроллерам или представлениям.
Hot Licks
4
@Daniel: добавление второго UIWindow хорошо работает для наложений, похожих на представление предупреждений.
Уилбур Вандрсмит

Ответы:

75

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

[UIApplication sharedApplication].keyWindow.rootViewController;

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

Уилбур Вандрсмит
источник
155
Уилбур, это даст вам противоположность того, о чем просила опера. rootViewController - это основной контроллер представления, а не самый верхний.
m4rkk
3
m4rkk: «Top-most» зависит от того, в каком направлении вы смотрите. Новые контроллеры добавляются сверху (в виде стека) или внизу (в виде дерева)? В любом случае, OP упомянул навигационный контроллер как находящийся сверху, что подразумевает вид сверху вниз.
Уилбур Вандрсмит
50
Слово «top» используется для контроллера представления, то есть визуально сверху (например -[UINavigationController topViewController]). Тогда есть слово «корень», который является корнем дерева (как -[UIWindow rootViewController].
Tricertops
13
@ImpurestClub Я не могу найти в документации , Xcode, похоже, не находит его.
Друкс
4
@adib нет, он принадлежит UINavigationController
Дэвид Х
428

Я думаю, что вам нужно сочетание принятого ответа и @ 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
}
Эрик
источник
4
Кроме того, вы можете проверить UINavigationControllerи попросить его topViewControllerили даже проверить UITabBarControllerи попросить selectedViewController. Это даст вам контроллер представления, который в данный момент виден пользователю.
Tricertops
33
Это неполное решение, поскольку оно пересекает только иерархию модально представленных контроллеров представления, а не иерархию childViewControllers (как используется UINavigationController, UITabBarController и т. Д.).
Водоросль
3
Это отличный способ абстрагироваться от представления контроллера модального представления, который возвращается к текущему состоянию приложения, в моем случае это был экран повторного ввода пароля после истечения времени ожидания приложения. Спасибо!
erversteeg
11
@algal: не совсем: UITabBarController, UINavigationController уже являются самыми верхними контроллерами представления в иерархии. В зависимости от того, что вы хотите сделать с «самым верхним контроллером», вы можете вообще не захотеть обходить их и возиться с их содержимым. В моем случае это было представить модальный контроллер поверх всего, и для этого мне нужно получить UINaviationController или UITabBarController, а не их содержимое !!
Rick77
1
@ Rick77, если это правда, твой один небольшой комментарий, скрытый здесь, делает ненужными тонны сложных изменений в других ответах. Поскольку никто больше не упоминает об этом вообще, я чувствую, что должен попросить вас подтвердить, что это правда. И если это так, это так важно, что он заслуживает того, чтобы быть ответом самостоятельно. Потому что подавляющее большинство других ответов делают сальто назад, пытаясь решить эту проблему. Вы бы спасали жизни!
Le Mot Juiced
150

Чтобы завершить ответ 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;
    }
}
Клео
источник
2
Хорошо, да, я забыл о контроллерах TabBar: P
JonasG
9
Не включает в себяchildViewControllers
Awesome-o
Посмотрите на мой ответ ниже, который улучшает ответ выше, обрабатывая пропущенные @kleo случаи, такие как всплывающие окна, добавляя контроллеры представления в качестве подпредставлений для некоторых других контроллеров представления при обходе
Rajesh
Если вы используете return [self topViewControllerWithRootViewController: navigationController.visibleViewController] ;, visibleViewController сам возвращает возвращаемый контроллер представления (ЕСЛИ ЛЮБОЙ), даже если это UIAlertController. Для тех, кому нужно избегать контроллера предупреждений пользовательского интерфейса, используйте topViewController вместо visibleViewController
Johnykutty
Просто чтобы добавить свои 50 центов к этому - я изо всех сил пытался заставить это работать в моем viewcontroller, который загружает веб-представление ... причина, почему я не мог заставить это работать, состояла в том, что представление все еще не было готово (не закончил загрузку) и поэтому это не было видно. Это привело к ситуации, когда получение topViewContoller не удавалось, потому что UINavigationController пытался получить видимый ViewController, пока еще не было видимого ViewController. Так что, если кто-то сталкивается с этой проблемой, убедитесь, что ваше представление завершает загрузку, прежде чем вызывать вышеупомянутый метод topViewController.
mbuster
52

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

  • Контроллер представления представляет другой вид
  • Контроллер представления является 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
    }
}
Ючен Чжун
источник
2
Я назвал его, visibleViewControllerчтобы было понятно, что он делает.
Джонни
31

Получение самого популярного контроллера представления для 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()
Варуна
источник
отлично - большое спасибо за это решение. Трюк subviews 'был необходим! Еще раз большое спасибо, вы спасли мой день.
ИКК
25

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

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

- (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];
Раджеш
источник
Также отсутствует SplitViewController?
Апиньо
21

Этот ответ включает 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;
}
Высокий-о
источник
Обновлен код, который также показывает, что это за контроллер, свернув и восстановив его снова. nik-kov-ios-developer.blogspot.ru/2016/12/…
Ник Ков
Эй, давай, где твой "topVisibleViewController"?
Рай
12

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

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

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

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

UIViewController *c = [UIViewController topMostController];
ipodishima
источник
10

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

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

JonasG
источник
7
С 1983 года я бы сказал. Помните, что Objective-C содержит C ... Обертывание кода ObjC в функции C - обычная практика, так что да, это код Objective-C.
JonasG
@JonasG Привет Джонас, В каких случаях вы предпочитаете упаковывать код ObjC в C? Потому что я иногда вижу такие функции C и не могу различить использование. Обеспечивает ли перенос кода в C какие-либо преимущества в производительности?
ОзБоз
1
@OzBoz В ситуациях, когда неясно, к какому классу он selfдолжен принадлежать.
Адиб
8

Вот мой взгляд на это. Спасибо @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;
}
Камран Хан
источник
Вы должны избегать именования методов, как getSomething:в Objective-C. Это имеет особое значение (подробнее: cocoadevcentral.com/articles/000082.php ), и вы не удовлетворяете этим требованиям в своем коде.
Вив
7
@implementation UIWindow (Расширения)

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

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

    вернуть topController;
}

@конец
FishStix
источник
Я не думаю, что вы выполнили условие, указанное в оригинальном сообщении.
Hot Licks
7
- (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;
    }
}
lifuqing_ios
источник
Я использовал это, но учтите, что он ломается, когда имеется более одного представленного контроллера представления
Чак Борис
7

Для последней версии 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.sharedApplication().delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

Используйте его где угодно как:

if let topVC = getTopViewController() {

}
Ashok
источник
Я не хочу менять ваш ответ слишком сильно, но предложил бы несколько вещей. 1. Добавить поддержку для UISplitViewController. 2. использовать switchвместо, если еще. 3. Я не уверен, что вам нужна статическая функция, я думаю, вы могли бы легко это сделать в объявленном вами уровне первого экземпляра var. 4. Вероятно, лучше не создавать слишком много глобальных функций, но это дело вкуса. Вы можете использовать одну строку кода для достижения эффекта глобальной функции:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Джордан Смит
7

Простое расширение для 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
}
Бартломей Семанчик
источник
ДА ДА ДА - в Интернете существует множество решений для поиска topMostViewController, но если в вашем приложении есть панель вкладок с вкладкой «Дополнительно», ВЫ ДОЛЖНЫ обрабатывать ее немного по-другому.
Энди Обусек,
7

Используйте расширение ниже, чтобы захватить текущий видимый 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()
COVID-19
источник
Не должен ли этот тест для presentedViewControllerпервых, до UINavigationControllerи UITabBarControllerслучаев? В противном случае, если контроллер представления модально представлен из UINavigationControllerили UITabBarController, он не будет возвращен как контроллер вида сверху, даже если виден контроллер представления.
Дрю
4

Еще одно решение 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)

}
Мартин Альгестен
источник
4

Расширение 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

Наслаждайтесь!

Saranjith
источник
1
@BilalBakhrom говорит «Upvoted. Я думаю, что ваш ответ - лучший. Вы не можете напрямую вызывать метод topViewController (). Класс UIApplication является singleton, используйте экземпляр с именем« shared ».» в редактировании, которое я проголосовал за отклонение. Если это действительно правильно, пожалуйста, нажмите здесь, чтобы перейти в меню, где вы можете одобрить это изменение.
wizzwizz4
3

Вот что сработало для меня.

Я обнаружил, что иногда в окне ключа контроллер был нулевым, поскольку keyWindow - это какая-то операционная система, например, оповещение и т. Д.

 + (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;
 }
Том Андерсен
источник
3

Расширяя ответ @ Эрика, вы должны быть осторожны, так как keyWindow - это действительно то окно, которое вы хотите. Если вы пытаетесь использовать этот метод после касания чего-либо, например, в представлении оповещений, то keyWindow фактически будет окном оповещения, и это, несомненно, вызовет проблемы. Это случилось со мной в дикой природе при обработке глубоких ссылок через оповещение и вызвало SIGABRT без использования STACK TRACE. Всего сука для отладки.

Вот код, который я сейчас использую:

- (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
источник
Вы нашли это полное решение? Многие другие ответы чрезвычайно сложны, и в них пытаются учесть так много крайних случаев. Я хочу, чтобы это было правдой, это так просто и элегантно.
Le Mot Juiced
У меня никогда не было проблем с этим. Если вы не делаете ничего необычного со своим стеком навигации, это должно сработать, в противном случае некоторые другие решения обрабатывают более сложные случаи.
Стакенборг
3

Альтернативное решение Swift:

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

    return topController!
}
Esqarrouth
источник
3

Это решение является наиболее полным. Это принимает во внимание: 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
}
Марк Рено
источник
3

Краткое, но всеобъемлющее решение в Swift 4.2, учитывает UINavigationControllers , UITabBarControllers , представленные и дочерние контроллеры представления:

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()
nalexn
источник
2

Отличное решение в 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
}
Эдвард Новело
источник
1

Я думаю, что большинство ответов полностью проигнорировано 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;
}
Аамир
источник
1

Я знаю, что это очень поздно и может быть излишним. Но вот фрагмент, который я придумал, который работает для меня:

    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
    }
Анил Аригела
источник
0

Это прекрасно работает для поиска 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]; 
johnnyg17
источник
0

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

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

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}
Толанд Хон
источник
0

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

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Тапас Пал
источник
Кроме того, если вы действительно прочитали вопрос, selfне имеет navigationControllerсвойства.
Hot Licks
0

Другое решение основано на цепочке респондента, которая может работать или не работать в зависимости от того, что является первым респондентом:

  1. Получите первый респондент .
  2. Получите 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;
}
Senseful
источник
0

Swift:

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)
        }
BOBJ-С
источник