Як знайти найкращий контролер перегляду на iOS


253

Зараз я зіткнувся з кількома випадками, коли було б зручно знайти «найвищий» контролер перегляду (той, що відповідає за поточний вигляд), але не знайшов способу це зробити.

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

Або, не маючи цього, чи можна знайти вид зверху?


Отже, ти кажеш, що це неможливо.
Гарячі лизи

@ Даніель ні, я кажу, що, здається, ваш код може скористатися певним перепроектуванням, тому що вам рідко потрібно знати це. Також ідея "найвищого" є справедливою лише у певних контекстах, і навіть тоді не завжди.
Дейв ДеЛонг

@Daniel Я неправильно прочитав ваше запитання. Є чимало прихильників, але намагаються відповісти на це. Це залежить від потоку контролера вашого перегляду. @ Відповідь Вільбура має стати гарною відправною точкою, щоб простежити це.
Діпак Дандупролу

Що ж, давайте спростимо це до конкретного випадку. Якби я хотів написати клон UIAlertView, як би це зробити? Зауважте, що він може функціонувати нормально, не передаючи адресування іншим контролерам або переглядам.
Гарячі лизи

4
@Daniel: Додавання другого вікна UIW добре спрацьовує для попереджувальних накладок на вигляд.
Вілбур Вандрсміт

Відповіді:


75

iOS 4 представив властивість rootViewController у UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

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


155
Вільбуре, це дасть тобі протилежне тому, що просили оп. rootViewController є базовим контролером, а не самим верхнім.
m4rkk

3
m4rkk: "Топ-найбільше" залежить від того, з якого напрямку ви дивитесь. Чи додаються нові контролери вгорі (схожі на стек) або внизу (як дерево)? У будь-якому випадку ОП згадував навігаційний контролер як верхній, що передбачає вигляд, що зростає вниз.
Вілбур Вандрсміт

50
Слово „верх” використовується для контролера перегляду, тобто візуально зверху (як -[UINavigationController topViewController]). Потім є слово „корінь”, що є коренем дерева (наприклад -[UIWindow rootViewController].
Tricertops

13
@ImpurestClub Я не в змозі знайти в документації , не здається, що Xcode його знаходить.
Друкс

4
@adib ні, він належить UINavigationController
David H

428

Я думаю, вам потрібна комбінація прийнятої відповіді та @ fishstix's

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

4
Крім того, ви можете перевірити UINavigationControllerі попросити його topViewControllerабо навіть перевірити UITabBarControllerі попросити selectedViewController. Це дозволить отримати контролер перегляду, який наразі видно користувачеві.
Tricertops

33
Це неповне рішення, оскільки воно переходить лише ієрархію модально представлених контролерів перегляду, а не ієрархію childViewControllers (як використовується UINavigationController, UITabBarController тощо).
algal

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, які не залишилися, наприклад, перехоплення, перегляд контролерів, доданих як підпогляди до деяких інших контролерів перегляду під час переходу
Раджеш,

Якщо ви використовуєте return [self topViewControllerWithRootViewController: navigationController.visibleViewController] ;, видимийViewController сам повертає представлений контролер перегляду (ЯКЩО ЯКЩО), навіть якщо це UIAlertController. Для тих, кому потрібно уникати контролера сповіщення інтерфейсу, використовуйте topViewController замість vidViewController
Johnykutty

Просто додати до цього свої 50 центів - я намагався дозволити це працювати в моєму контролері перегляду, який завантажує webView. і тому його не було видно Це призвело до ситуації, коли отримати topViewContoller не вдалося, тому що UINavigationController намагався отримати видимий ViewController, поки ще не було видимого ViewController. Тож якщо хтось стикається з цією проблемою, переконайтеся, що ваш погляд закінчується завантаженням, перш ніж здійснити виклик вищевказаному методу topViewController.
mbuster

52

Повна нерекурсивна версія, опікуючись різними сценаріями:

  • Контролер перегляду представляє інший вигляд
  • Контролер перегляду є a UINavigationController
  • Контролер перегляду є a UITabBarController

Ціль-С

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

відмінно - дуже дякую за це рішення. Субспецензії не потрібні! Знову, велике спасибі, ти врятував мені день.
iKK

25

Для завершення Еріка відповіді (який покинув з Popovers, навігаційні контролери, tabbarcontrollers, вид контролерів доданий в якості підвидів в деяких інших контролери відображення у час проходження), ось моя версія про повернення видимого в даний момент контролера уявлення:

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

- (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/…
Nik Kov

Гей, давай, де твій "topVisibleViewController"?
Рай

12

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

Тому я випустив свій код, який досить простий і фактично заснований на протоколі, так що він є гнучким для кожного типу контролера контейнерів. Здається, це пов'язано з останніми відповідями, але набагато гнучкішим чином.

Ви можете захопити код тут: PPTopMostController

І отримав найкращий контролер, який використовує

UIViewController *c = [UIViewController topMostController];

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 повинен бути повернутий!


7
З 1983 року я б сказав. Пам'ятайте, що Objective-C містить C ... Загортання коду ObjC у функції C є звичайною практикою, так що так, це код Objective-C.
JonasG

@JonasG Привіт, Джонасе, за яких обставин ви віддаєте перевагу загортанню коду ObjC в C? Тому що я іноді бачу такі функції C, як ця, і не можу розрізнити використання. Чи забезпечує упаковковий код на C якісь переваги від продуктивності?
OzBoz

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 ), і ви не задовольняєте цим вимогам у своєму коді.
Vive

7
@implementation UIWindow (розширення)

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

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

    повернути верхнійконтролер;
}

@end

Я не думаю, що ви задовольнили умову, викладену в оригінальній публікації.
Гарячі лизи

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

Я використав це, але зауважте, що він порушується, коли є більше одного представленого контролера перегляду
Чак Борис,

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

}

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

7

Просте розширення для UIApplicationSwift:

ПРИМІТКА:

Це хвилює 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()

Чи не слід це тестувати 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

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


1
@BilalBakhrom каже: "Оновлено. Я думаю, що ваша відповідь найкраща. Ви не можете викликати безпосередньо метод topViewController (). Клас UIApplication є однотонним, використовуйте екземпляр, який називається" спільний ". у редакції, яку я проголосував за відхилення. Якщо це дійсно правильно, натисніть тут, щоб перейти до меню, де ви можете затвердити це редагування.
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 насправді буде вікном сповіщення, і це безсумнівно спричинить проблеми. Це трапилося зі мною в дикій природі під час обробки глибоких посилань через оповіщення і викликало СІГАБРТИ, ЩО НЕ СТАЛО СЛІД. Загальна сука для налагодження.

Ось код, який я зараз використовую:

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

Не соромтесь змішувати це з будь-яким смаком отримання контролера виду зверху, який вам подобається, з інших відповідей на це питання.


Ви вважаєте це повним рішенням? Так багато інших відповідей надзвичайно складні, намагаючись врахувати стільки кращих справ. Я хочу, щоб це було правдою, це так просто і елегантно.
Le Mot Juiced

У мене ніколи не було проблем з цим. Якщо ви не робите нічого незвичного зі своїм стеком nav, це має спрацювати, інакше деякі інші рішення стосуються складніших випадків.
Стакенборг

3

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

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

    return topController!
}

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

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

Це чудово підходить для пошуку вершини 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]; 

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властивості.
Гарячі лизи

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

0

Швидкий:

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)
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.