Comment trouver le contrôleur de vue le plus haut sur iOS

253

J'ai rencontré quelques cas où il serait pratique de pouvoir trouver le contrôleur de vue "le plus haut" (celui responsable de la vue actuelle), mais je n'ai pas trouvé de moyen de le faire.

Fondamentalement, le défi est le suivant: étant donné que l'on exécute dans une classe qui n'est pas un contrôleur de vue (ou une vue) [et qui n'a pas l'adresse d'une vue active] et qui n'a pas reçu l'adresse du contrôleur de vue le plus haut ( ou, par exemple, l'adresse du contrôleur de navigation), est-il possible de trouver ce contrôleur de vue? (Et si oui, comment?)

Ou, à défaut, est-il possible de trouver la vue la plus haute?

Hot Licks
la source
Vous dites donc que ce n'est pas possible.
Hot Licks
@ Daniel non, je dis qu'il semble que votre code pourrait utiliser une nouvelle conception, car vous devriez rarement avoir besoin de le savoir. De plus, l'idée de "topmost" n'est valable que dans certains contextes, et même pas toujours.
Dave DeLong
@Daniel J'ai mal lu votre question. Il y a beaucoup de si et de mais essayant de répondre à celui-ci. Cela dépend de votre flux de contrôleur de vue. @ La réponse de Wilbur devrait être un bon point de départ pour la retrouver.
Deepak Danduprolu
Eh bien, simplifions-le à un cas spécifique. Si je voulais écrire un clone de UIAlertView, comment le ferais-je? Notez qu'il peut fonctionner correctement sans être adressé à d'autres contrôleurs ou vues.
Hot Licks
4
@Daniel: L'ajout d'une deuxième UIWindow fonctionne bien pour les superpositions de type alerte.
Wilbur Vandrsmith

Réponses:

75

iOS 4 a introduit la propriété rootViewController sur UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

Vous devrez cependant le configurer vous-même après avoir créé le contrôleur de vue.

Wilbur Vandrsmith
la source
155
Wilbur, cela vous donnera l'opposé de ce que l'op a demandé. rootViewController est le contrôleur de vue de base plutôt que le plus haut.
m4rkk
3
m4rkk: "Top-most" dépend de la direction dans laquelle vous regardez. De nouveaux contrôleurs sont-ils ajoutés en haut (en forme de pile) ou en bas (en forme d'arbre)? Dans tous les cas, l'OP a mentionné que le contrôleur de navigation était en haut, ce qui implique une vue vers le bas.
Wilbur Vandrsmith
50
Le mot «top» est utilisé pour le contrôleur de vue, c'est-à-dire visuellement en haut (comme -[UINavigationController topViewController]). Ensuite, il y a le mot «racine», qui est la racine de l'arbre (comme -[UIWindow rootViewController].
Tricertops
13
@ImpurestClub Je ne trouve pas dans la documentation , Xcode ne semble pas le trouver.
Drux
4
@adib non, il appartient à UINavigationController
David H
428

Je pense que vous avez besoin d'une combinaison de la réponse acceptée et de @ fishstix

+ (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
}
Eric
la source
4
De plus, vous pouvez vérifier UINavigationControlleret demander son topViewControllerou même vérifier UITabBarControlleret demander selectedViewController. Cela vous donnera le contrôleur de vue actuellement visible par l'utilisateur.
Tricertops
33
Il s'agit d'une solution incomplète, car elle ne traverse que la hiérarchie des contrôleurs de vue présentés de façon modale, pas la hiérarchie de childViewControllers (telle qu'utilisée par UINavigationController, UITabBarController, etc.).
algal
3
C'est un excellent moyen de résumer la présentation d'un contrôleur de vue modale qui reprend l'état actuel de l'application, dans mon cas, c'était un écran de réentrée de mot de passe après le délai d'expiration de l'application. Merci!
erversteeg
11
@algal: pas vraiment: UITabBarController, UINavigationController sont déjà les contrôleurs de vue les plus élevés de la hiérarchie. Selon ce que vous voulez faire avec le "contrôleur le plus haut", vous ne voudrez peut-être pas les parcourir du tout et jouer avec leur contenu. Dans mon cas, c'était pour présenter un contrôleur modal par dessus tout, et pour cela j'ai besoin d'obtenir l'UINaviationController ou UITabBarController, pas leur contenu !!
Rick77
1
@ Rick77, si c'est vrai, votre seul petit commentaire enterré ici rend inutiles les tonnes de modifications compliquées dans les autres réponses. Puisque personne d'autre ne le mentionne du tout, je sens que je dois vous demander d'affirmer que c'est vrai. Et si c'est le cas, c'est tellement important qu'il mérite d'être une réponse à part entière. Parce que la grande majorité des autres réponses font des backflips en essayant de résoudre ce problème. Vous sauveriez des vies!
Le Mot Juiced
150

Pour compléter la réponse de JonasG (qui a omis les contrôleurs de la barre d'onglets pendant la traversée), voici ma version de retour du contrôleur de vue actuellement visible:

- (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;
    }
}
kleo
la source
2
Bien, ouais j'ai oublié les contrôleurs TabBar: P
JonasG
9
Ne comprend paschildViewControllers
Awesome-o
Regardez ma réponse ci-dessous qui améliore la réponse ci-dessus en gérant les cas @kleo omis tels que les popovers, les contrôleurs de vue ajoutés en tant que sous-vues à d'autres contrôleurs de vue pendant la traversée
Rajesh
Si vous utilisez return [self topViewControllerWithRootViewController: navigationController.visibleViewController] ;, visibleViewController lui-même renvoyant le contrôleur de vue présenté (IF ANY), même s'il s'agit d'un UIAlertController. Pour quelqu'un qui doit éviter le contrôleur d'alerte ui, utilisez topViewController au lieu de visibleViewController
Johnykutty
Juste pour ajouter mes 50 cents à cela - je luttais pour que cela fonctionne dans mon viewcontroller qui charge une webView. et donc ce n'était pas visible. Cela a conduit à une situation où l'obtention d'un topViewContoller échouait, car l'UINavigationController essayait d'obtenir un ViewController visible alors qu'il n'y avait pas encore de ViewController visible. Par conséquent, si quelqu'un est confronté à ce problème, assurez-vous que le chargement de votre vue se termine avant d'appeler la méthode topViewController ci-dessus.
mbuster
52

Une version complète non récursive, prenant en charge différents scénarios:

  • Le contrôleur de vue présente une autre vue
  • Le contrôleur de vue est un UINavigationController
  • Le contrôleur de vue est un UITabBarController

Objectif 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
    }
}
Yuchen Zhong
la source
2
Je l'ai nommé visibleViewControllerpour que ce soit clair.
Jonny
31

Obtenir le meilleur contrôleur de vue pour Swift à l'aide d'extensions

Code:

extension UIViewController {
    @objc func topMostViewController() -> UIViewController {
        // Handling Modal views
        if let presentedViewController = self.presentedViewController {
            return presentedViewController.topMostViewController()
        }
        // Handling UIViewController's added as subviews to some other views.
        else {
            for view in self.view.subviews
            {
                // Key property which most of us are unaware of / rarely use.
                if let subViewController = view.next {
                    if subViewController is UIViewController {
                        let viewController = subViewController as! UIViewController
                        return viewController.topMostViewController()
                    }
                }
            }
            return self
        }
    }
}

extension UITabBarController {
    override func topMostViewController() -> UIViewController {
        return self.selectedViewController!.topMostViewController()
    }
}

extension UINavigationController {
    override func topMostViewController() -> UIViewController {
        return self.visibleViewController!.topMostViewController()
    }
}

Usage:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()
Varuna
la source
excellent - merci beaucoup pour cette solution. Le tour des sous-vues était nécessaire! Encore une fois, merci beaucoup, vous m'avez sauvé la journée.
iKK
25

Pour compléter la réponse d'Eric (qui a omis les popovers, les contrôleurs de navigation, les tabbarcontrollers, les contrôleurs de vue ajoutés en tant que sous-vues à d'autres contrôleurs de vue pendant la traversée), voici ma version de retour du contrôleur de vue actuellement visible:

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

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

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

Et maintenant, tout ce que vous devez faire pour obtenir le meilleur contrôleur de vue est d'appeler la méthode ci-dessus comme suit:

UIViewController *topMostViewControllerObj = [self topViewController];
Rajesh
la source
Il manque également SplitViewController?
apinho
21

Cette réponse inclut childViewControllerset maintient une implémentation propre et lisible.

+ (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;
}
Génial-o
la source
Mis à jour du code, car il montre également de quel contrôleur il s'agit en minimisant et en le restaurant à nouveau. nik-kov-ios-developer.blogspot.ru/2016/12/…
Nik Kov
Hé, allez, où est votre "topVisibleViewController"?
Paradise
12

J'ai récemment rencontré cette situation dans un de mes projets, qui exigeait d'afficher une vue de notification quel que soit le contrôleur affiché et quel que soit le type (UINavigationController, contrôleur classique ou contrôleur de vue personnalisé), lorsque l'état du réseau changeait.

Je viens donc de publier mon code, qui est assez simple et basé sur un protocole afin qu'il soit flexible avec chaque type de contrôleur de conteneur. Il semble être lié aux dernières réponses, mais de manière beaucoup plus flexible.

Vous pouvez récupérer le code ici: PPTopMostController

Et j'ai obtenu le meilleur contrôleur en utilisant

UIViewController *c = [UIViewController topMostController];
ipodishima
la source
10

Ceci est une amélioration de la réponse d'Eric:

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) est une fonction d'aide.

Maintenant, tout ce que vous avez à faire est d'appeler topMostController()et le plus haut UIViewController devrait être retourné!

JonasG
la source
7
Depuis 1983, je dirais. Rappelez-vous qu'Objective-C contient C ... L'encapsulation du code ObjC dans les fonctions C est une pratique courante, alors oui, c'est du code Objective-C.
JonasG
@ JonasG Salut Jonas, Dans quelles circonstances préfères-tu encapsuler du code ObjC en C? Parce que, je vois parfois des fonctions C comme ça et ne peux pas distinguer l'utilisation. L'encapsulation de code en C offre-t-elle des avantages en termes de performances?
OzBoz
1
@OzBoz Dans les situations où il n'est pas immédiatement clair à quelle classe selfdoit appartenir.
adib
8

Voici mon point de vue à ce sujet. Merci à @Stakenborg d'avoir indiqué la façon d'éviter d'opter pour UIAlertView comme le plus grand contrôleur

-(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;
}
Kamran Khan
la source
Vous devez éviter de nommer les méthodes comme getSomething:dans Objective-C. Cela a une signification particulière (plus: cocoadevcentral.com/articles/000082.php ) et vous ne remplissez pas ces exigences dans votre code.
Vive
7
@implementation UIWindow (extensions)

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

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

    return topController;
}

@fin
FishStix
la source
Je ne pense pas que vous ayez satisfait à la condition énoncée dans le message d'origine.
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
la source
Je l'ai utilisé, mais notez qu'il se casse lorsqu'il y a plus d'un contrôleur de vue présenté
Chuck Boris
7

Pour la dernière version de Swift:
créez un fichier, nommez-le UIWindowExtension.swiftet collez l'extrait de code suivant:

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
}

Utilisez-le n'importe où comme:

if let topVC = getTopViewController() {

}
Ashok
la source
Je ne veux pas trop changer votre réponse, mais je suggérerais quelques choses. 1. Ajoutez la prise en charge d'UISplitViewController. 2. utiliser à la switchplace de if else. 3. Je ne suis pas sûr que vous ayez également besoin d'une fonction statique, je pense que vous pourriez le faire facilement dans le premier niveau var que vous avez déclaré. 4. Il vaut probablement mieux ne pas créer trop de fonctions globales mais c'est une question de goût. Vous pouvez utiliser une ligne de code pour obtenir l'effet de la fonction globale:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Jordan Smith
7

Extension simple pour UIApplicationdans Swift:

REMARQUE:

Ça tient à l' moreNavigationControllerintérieurUITabBarController

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

Utilisation simple:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}
Bartłomiej Semańczyk
la source
OUI OUI OUI - il existe de nombreuses solutions sur le Web pour trouver le topMostViewController mais si votre application possède une barre d'onglets avec un onglet Plus, VOUS DEVEZ le gérer un peu différemment.
Andy Obusek
7

Utilisez l'extension ci-dessous pour saisir le courant visible UIViewController. A travaillé pour Swift 4.0 et versions ultérieures

Swift 4.0 et versions ultérieures:

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

Comment utiliser?

let objViewcontroller = UIApplication.topViewController()
COVID-19 [FEMININE
la source
Ce test ne devrait-il pas d' presentedViewControllerabord, avant les cas UINavigationControlleret UITabBarController? Sinon, si un contrôleur de vue est présenté de manière modale à partir d'un UINavigationControllerou UITabBarController, il ne sera pas renvoyé en tant que contrôleur de vue de dessus, même si c'est le contrôleur de vue qui est visible.
Drew
4

Encore une autre solution 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)

}
Martin Algesten
la source
4

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

Utilisez-le de n'importe où comme,

 UIApplication.topViewController()?.present(yourController, animated: true, completion: nil)

ou comme,

 UIApplication.topViewController()?
                    .navigationController?
                    .popToViewController(yourController,
                                         animated: true)

Convient à toutes les classes comme UINavigationController, UITabBarController

Prendre plaisir!

Saranjith
la source
1
@BilalBakhrom dit "Upvoted. Je pense que votre réponse est la meilleure. Vous ne pouvez pas appeler directement la méthode topViewController (). La classe UIApplication est singleton, utilisez une instance appelée" shared "." dans un montage que j'ai voté contre. Si cela est correct, veuillez cliquer ici pour accéder à un menu où vous pouvez approuver cette modification.
wizzwizz4
3

Voici ce qui a fonctionné pour moi.

J'ai trouvé que parfois le contrôleur était nul sur la fenêtre de clé, car le keyWindow est quelque chose d'OS comme une alerte, etc.

 + (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;
 }
Tom Andersen
la source
3

En développant la réponse de @ Eric, vous devez faire attention à ce que keyWindow soit en fait la fenêtre que vous souhaitez. Si vous essayez d'utiliser cette méthode après avoir tapé quelque chose dans une vue d'alerte par exemple, le keyWindow sera en fait la fenêtre de l'alerte, et cela vous causera sans aucun doute des problèmes. Cela m'est arrivé à l'état sauvage lors de la gestion des liens profonds via une alerte et a provoqué des SIGABRT avec NO STACK TRACE. Salope totale à déboguer.

Voici le code que j'utilise maintenant:

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

N'hésitez pas à mélanger cela avec n'importe quelle saveur de récupération du contrôleur de vue de dessus que vous aimez dans les autres réponses à cette question.

Stakenborg
la source
Avez-vous trouvé que c'était une solution complète? Beaucoup d'autres réponses sont extrêmement compliquées, essayant de tenir compte de tant de cas marginaux. Je veux que ce soit vrai, c'est tellement simple et élégant.
Le Mot Juiced
Je n'ai jamais eu de problème avec ça. Si vous ne faites rien d'inhabituel avec votre pile de navigation, cela devrait fonctionner, sinon certaines des autres solutions gèrent des cas plus compliqués.
Stakenborg
3

Solution alternative rapide:

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

    return topController!
}
Esqarrouth
la source
3

Cette solution est la plus complète. Il prend en considération: UINavigationController UIPageViewController UITabBarController Et le contrôleur de vue présenté le plus haut du contrôleur de vue de dessus

L'exemple est dans Swift 3.

Il y a 3 surcharges

//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
}
Marc Renaud
la source
3

Une solution concise mais complète dans Swift 4.2, prend en compte les contrôleurs UINavigationControllers , UITabBarControllers , présentés et enfants :

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

Usage:

let viewController = UIApplication.shared.topmostViewController()
nalexn
la source
2

Grande solution dans Swift, implémenter dans 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
}
Edward Novelo
la source
1

Je pense que la plupart des réponses ont complètement ignoré UINavigationViewController, j'ai donc géré ce cas d'utilisation avec l'implémentation suivante.

+ (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;
}
Aamir
la source
1

Je sais que c'est très tard et peut être redondant. Mais voici l'extrait de code que j'ai trouvé et qui fonctionne pour moi:

    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
    }
Anil Arigela
la source
0

Cela fonctionne très bien pour trouver le viewController 1 supérieur à partir de n'importe quel contrôle de vue racine

+ (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
la source
0

Je ne sais pas si cela aidera ce que vous essayez d'accomplir en trouvant le contrôleur de vue le plus haut, mais j'essayais de présenter un nouveau contrôleur de vue, mais si mon contrôleur de vue racine avait déjà une boîte de dialogue modale, il serait bloqué, donc je passerait en haut de tous les contrôleurs de vue modale à l'aide de ce code:

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

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}
Toland Hon
la source
0

vous pouvez trouver le meilleur contrôleur de vue en utilisant

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Tapas Pal
la source
Sauf que si vous lisez réellement la question, elle selfn'a aucune navigationControllerpropriété.
Hot Licks
0

Une autre solution repose sur la chaîne de répondeurs, qui peut ou non fonctionner selon ce qu'est le premier répondant:

  1. Obtenez le premier répondant .
  2. Obtenez l'UIViewController associé à ce premier répondant .

Exemple de pseudo code:

+ (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;
}
Sensé
la source
0

Rapide:

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

Usage:

 if let topController = window.visibleViewController() {
            println(topController)
        }
Bobj-C
la source