[ios] iPhone - Comment trouver le contrôleur de vue le plus haut


14 Answers

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

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

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

    return topController;
}

Swift 3.0+

let rootViewController = UIApplication.shared.keyWindow?.rootViewController
Question

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

Fondamentalement, le défi est le suivant: étant donné que l'on s'exécute dans une classe qui n'est pas un contrôleur de vue (ou une vue) [et n'a pas l'adresse d'une vue active] et n'a pas reçu l'adresse du contrôleur de vue supérieur ( ou, disons, 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?




Au-dessous de deux fonction peut aider à trouver le topViewController sur les contrôleurs de pile de vue. Vous aurez peut-être besoin de personnalisation plus tard, mais pour ce code, il est génial de comprendre le concept de topViewController ou la pile de viewControllers.

- (UIViewController*)findTopViewController {

  id  topControler  = [self topMostController];

  UIViewController* topViewController;
  if([topControler isKindOfClass:[UINavigationController class]]) {
        topViewController = [[(UINavigationController*)topControler viewControllers] lastObject];
   } else if ([topControler isKindOfClass:[UITabBarController class]]) {
        //Here you can get reference of top viewcontroller from stack of viewcontrollers on UITabBarController
  } else {
        //topController is a preented viewController
        topViewController = (UIViewController*)topControler;
  }
    //NSLog(@"Top ViewController is: %@",NSStringFromClass([topController class]));
    return topViewController;
}

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

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }
    //NSLog(@"Top View is: %@",NSStringFromClass([topController class]));
    return topController;
}

You can use [viewController Class] method to find out the type of class of a viewController.




Extension simple pour UIApplication dans Swift:

REMARQUE:

Il s'intéresse à moreNavigationController dans 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
    }
}

Utilisation simple:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}



This works great for finding the top viewController 1 from any root view controlle

+ (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]; 



Pour la dernière version de Swift:
Créez un fichier, nommez-le UIWindowExtension.swift et collez l'extrait 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 partout comme:

if let topVC = getTopViewController() {

}



J'ai récemment eu cette situation dans mon projet, qui nécessitait 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 a changé.

Je viens donc de publier mon code, ce qui est assez simple et basé sur un protocole qui le rend flexible avec chaque type de contrôleur de conteneur. Il semble être lié aux dernières réponses, mais de manière très flexible.

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

Et a obtenu le plus haut contrôleur en utilisant

UIViewController *c = [UIViewController topMostController];



Obtenir le plus haut contrôleur de vue pour Swift en utilisant des 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()



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
}



Une autre solution repose sur la chaîne de réponse, qui peut ou peut ne pas fonctionner selon ce que le premier répondeur est:

  1. Obtenez le premier répondeur .
  2. Obtenez le UIViewController associé à ce premier répondeur .

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



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



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



En développant la réponse de @ Eric, vous devez faire attention à ce que la fenêtre keyWindow soit en fait la fenêtre que vous voulez. Si vous essayez d'utiliser cette méthode après avoir tapé quelque chose dans une vue d'alerte par exemple, keyWindow sera en fait la fenêtre de l'alerte, et cela ne vous posera aucun problème. Cela m'est arrivé à l'état sauvage lors de la manipulation de liens profonds via une alerte et causé 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 ceci avec n'importe quelle saveur de récupérer le contrôleur de vue de dessus que vous aimez des autres réponses sur cette question.




Je ne sais pas si cela va aider 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à un dialogue modal, il serait bloqué. ferait un cycle vers le haut de tous les contrôleurs de vue modale en utilisant ce code:

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

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}



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)

}



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

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

... Code for reference/comparison:

UIViewController *(^topmostViewControllerForFrontmostNormalLevelWindowBlock)(void)=^UIViewController * // NOTE: Adapted from various stray answers here: https://.com/questions/6131205/iphone-how-to-find-topmost-view-controller/20515681
{
    __block UIViewController *viewController;

    [[[[[UIApplication sharedApplication] windows] reverseObjectEnumerator] allObjects] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop)
    {
        if ([window windowLevel]==UIWindowLevelNormal)
        {
            viewController=[window rootViewController];
            ////

            *stop=YES;

        }

    } ];

    while (viewController)
    {
        if ([viewController isKindOfClass:[UITabBarController class]])
        {
            viewController=[(UITabBarController *)viewController selectedViewController];

        }
        else if ([viewController isKindOfClass:[UINavigationController class]])
        {
            viewController=[(UINavigationController *)viewController visibleViewController];

        }
        else if ([viewController presentedViewController] && ![[viewController presentedViewController] isBeingDismissed])
        {
            viewController=[viewController presentedViewController];

        }
        else if ([[viewController childViewControllers] count]>0)
        {
            viewController=[[viewController childViewControllers] lastObject];

        }
        else
        {
            __block BOOL needsRepeat=NO;

            [[[[[viewController view] subviews] reverseObjectEnumerator] allObjects] enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop)
            {
                if ([[view nextResponder] isKindOfClass:[UIViewController class]])
                {
                    viewController=(UIViewController *)[view nextResponder];

                    needsRepeat=YES;
                    ////

                    *stop=YES;

                }

            } ];

            if (!needsRepeat)
            {
                break;

            }

        }

    }

    return viewController;

};



Related