Obtenez le meilleur UIViewController


204

Je n'arrive pas à tirer le meilleur parti UIViewControllersans accès à un fichier UINavigationController. Voici ce que j'ai jusqu'à présent:

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

Cependant, cela ne semble rien faire. Les keyWindowet rootViewControllersemblent être des valeurs non nulles également, donc le chaînage facultatif ne devrait pas être un problème.

REMARQUE: faire quelque chose comme ça est une mauvaise idée Il brise le modèle MVC.


Voici une solution alternative disponible stackoverflow.com/a/39994115/1872233
iDevAmit

Réponses:


309

presentViewControllermontre un contrôleur de vue. Il ne renvoie pas de contrôleur de vue. Si vous n'utilisez pas a UINavigationController, vous recherchez probablement presentedViewControlleret vous devrez commencer à la racine et parcourir les vues présentées.

if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

Pour Swift 3+:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

Pour iOS 13+

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

if var topController = keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

// topController should now be your topmost view controller
}

2
Quelqu'un peut-il expliquer la boucle while? Pour moi, on dirait qu'il n'y a rien à boucler; Je ne sais même pas pourquoi cela compile.
Professeur Tom

15
@ProfessorTom La boucle continue tant que topController.presentedViewControllerrenvoie quelque chose (c'est-à-dire que le contrôleur a un contrôleur enfant présenté). C'est while letpour faire respecter le fait qu'il topController.presentedViewControllerfaut retourner quelque chose. S'il renvoie nil (c'est-à-dire que le contrôleur n'a aucun enfant présenté), alors il arrêtera la boucle. Dans le corps de la boucle, il réaffecte l'enfant en tant que courant topController, et effectue à nouveau une boucle en descendant la hiérarchie du contrôleur de vue. Il peut être réaffecté topControllercomme c'est un vardans l' ifinstruction externe .
rickerbh

1
Merci. Je n'ai pas pu trouver d'exemples en ligne while let. Il y a, bien sûr, beaucoup d' if letexemples à trouver.
Professeur Tom

1
La let x = somethingThatCouldBeNilsyntaxe est une astuce très pratique à utiliser partout où une valeur / condition de vérité pourrait être utilisée. Si nous ne l'utilisions pas ici, nous devrons attribuer explicitement une valeur, puis tester pour voir si elle est réellement là. Je pense que c'est vraiment succinct et expressif.
rickerbh

1
Je connais le truc, c'est juste un peu plus difficile de raisonner sur les boucles while - pour lesquelles j'ai trouvé une pénurie d'exemples - en particulier celui-ci.
Professeur Tom

276

avoir cette extension

Swift 2. *

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(presented)
        }
        return controller
    }
}

Swift 3

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

Vous pouvez l'utiliser n'importe où sur votre manette

if let topController = UIApplication.topViewController() {

}

1
Merci pour votre astuce d'extension :)
Thein

4
J'ai tenté d'apporter une modification importante à cette réponse, mais elle a été rejetée (je ne sais pas pourquoi et les raisons du modèle données n'avaient pas de sens): il est important de vérifier si le nav.visibleViewController est nul avant de l'utiliser dans le récursif call (tout comme tab.selectedViewController est vérifié) car sinon, s'il était nul, vous vous retrouveriez dans une boucle infinie récursive.
Ethan G

@EthanG Selon ma compréhension, si nav.visibleViewController est nul, la fonction renverra nil (tomber au dernier return). Comment peut-il entrer dans une boucle infinie?
Desmond DAI

3
Je pense qu'il serait plus logique d'en faire une fonction statique de UIViewController
Leszek Zarna

1
La vérification 'presentViewController' devrait probablement venir en premier si vous voulez attraper les contrôleurs de vue présentés de manière modale sur UITabBarControllers ..
Tokuriku

73

Pour swift 4/5 + pour obtenir une vue optimale

// MARK: UIApplication extensions

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)

        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)

        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

Comment utiliser

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}

2
Solution géniale. Merci!
Andrey M.

2
«keyWindow» était obsolète dans iOS 13.0.
rs7

2
'keyWindow' est obsolète dans iOS 13.0 stackoverflow.com/a/57899013/4514671
Rebeloper

19
extension UIWindow {

    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        if let navigationController = vc as? UINavigationController,
            let visibleController = navigationController.visibleViewController  {
            return UIWindow.getVisibleViewControllerFrom( vc: visibleController )
        } else if let tabBarController = vc as? UITabBarController,
            let selectedTabController = tabBarController.selectedViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: selectedTabController )
        } else {
            if let presentedViewController = vc.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
            } else {
                return vc
            }
        }
    }
}

Usage:

if let topController = window.visibleViewController() {
    println(topController)
}

cette solution semblait vraiment prometteuse, mais j'ai essayé de l'exécuter pour obtenir le contrôleur de vue sur lequel je suis lorsque je reçois une notification push et cela a généré une erreur nulle sur lereturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike

@Mike, vous devez utiliser uniquement presentViewController, pas presentViewController. présentéViewController
allaire

@allaire Si vous avez présenté un contrôleur de vue modale au-dessus d'un contrôleur de vue modale, alors vous avez besoin .presentedViewController.presentedViewController, ou pas?
Baran Emre

6

Basé sur la réponse de Dianz, la version Objective-C

- (UIViewController *) topViewController {
   UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
   if ([baseVC isKindOfClass:[UINavigationController class]]) {
       return ((UINavigationController *)baseVC).visibleViewController;
   }

   if ([baseVC isKindOfClass:[UITabBarController class]]) {
       UIViewController *selectedTVC = ((UITabBarController*)baseVC).selectedViewController;
       if (selectedTVC) {
           return selectedTVC;
       }
   }

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}

Ne fonctionnera pas pour UINavigationController dans UITabBarController. il retournera UINavigationController, devrait renvoyer le topController dans la navigation bloquée.
Mike.R

Tnx Tnx Tnx Bro
reza_khalafi

6

J'ai adoré la réponse de @ dianz , et voici donc la version de Swift 3. C'est fondamentalement la même chose mais il lui manquait une accolade et certains des noms de syntaxe / variable / méthode ont changé. Alors voilà!

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

L'utilisation est toujours la même:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}

6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 J'ai fait quelques tests sur les réponses et commentaires sur ce site. Pour moi, les travaux suivants

extension UIViewController {
    func topMostViewController() -> UIViewController {

        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }

        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }

        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }

        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

Ensuite, obtenez la vue de dessusController en:

UIApplication.shared.topMostViewController()

5

Utilisez ce code pour trouver les meilleurs UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}

2
En quoi est-ce différent de la réponse de Rickerbh?
ElectroBuddha

5

Légère variation sur @AlberZou en utilisant une variable calculée plutôt qu'une fonction

extension UIViewController {
  var topMostViewController : UIViewController {

    if let presented = self.presentedViewController {
      return presented.topMostViewController
    }

    if let navigation = self as? UINavigationController {
      return navigation.visibleViewController?.topMostViewController ?? navigation
    }

    if let tab = self as? UITabBarController {
      return tab.selectedViewController?.topMostViewController ?? tab
    }

    return self
  }
}

extension UIApplication {
  var topMostViewController : UIViewController? {
    return self.keyWindow?.rootViewController?.topMostViewController
  }
}

Alors dire

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}

4

Basé sur Bob -c ci-dessus:

Swift 3.0

extension UIWindow {


    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController  = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {

        if vc.isKind(of: UINavigationController.self) {

            let navigationController = vc as! UINavigationController
            return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)

        } else if vc.isKind(of: UITabBarController.self) {

            let tabBarController = vc as! UITabBarController
            return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)

        } else {

            if let presentedViewController = vc.presentedViewController {

                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)

            } else {

                return vc;
            }
        }
    }
}

4

Trop de saveurs mais aucune une élaborée itérative. Combiné des précédents:

     func topMostController() -> UIViewController? {
        var from = UIApplication.shared.keyWindow?.rootViewController
        while (from != nil) {
            if let to = (from as? UITabBarController)?.selectedViewController {
                from = to
            } else if let to = (from as? UINavigationController)?.visibleViewController {
                from = to
            } else if let to = from?.presentedViewController {
                from = to
            } else {
                break
            }
        }
        return from
    }

3
import UIKit

extension UIApplication {

    // MARK: Choose keyWindow as per your choice
    var currentWindow: UIWindow? {
        connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first
    }

    // MARK: Choose keyWindow as per your choice
    var keyWindow: UIWindow? {
        UIApplication.shared.windows.first { $0.isKeyWindow }
    }

    class func topMostViewController(base: UIViewController? = UIApplication.shared.currentWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topMostViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController, top.view.window != nil {
                return topMostViewController(base: top)
            } else if let selected = tab.selectedViewController {
                return topMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topMostViewController(base: presented)
        }
        return base
    }
}

Utilisation ambiguë de 'visibleViewController'
Omar N Shamali

2

vous pouvez définir une variable UIViewController dans AppDelegate, et dans chaque vueWillAppear définira la variable sur self (cependant, la réponse dianz est la meilleure réponse).

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}

1
merci beaucoup cela fonctionne très bien pour moi comme l'autre solution quand il essaie d'obtenir la navigationContrôler le retour nil donc je n'ai pas pu pousser de nouveau vc
Amr Angry

Assurez-vous que le currentVC est défini comme une référence faible, sinon vous aurez une fuite de mémoire.
bubuxu

2

Pour trouver le viewController visible dans Swift 3

if let viewControllers = window?.rootViewController?.childViewControllers {

     let prefs = UserDefaults.standard

     if viewControllers[viewControllers.count - 1] is ABCController{
        print("[ABCController] is visible")

     }
}

Ce code trouve le dernier contrôleur ajouté ou le dernier contrôleur actif visible.

Ceci que j'ai utilisé dans AppDelegate pour trouver le contrôleur de vue active


2

Pour Swift 5+ , iOS 13+

extension UIViewController {
    static func topMostViewController() -> UIViewController? {
        if #available(iOS 13.0, *) {
            let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
            return keyWindow?.rootViewController?.topMostViewController()
        }
        
        return UIApplication.shared.keyWindow?.rootViewController?.topMostViewController()
    }
    
    func topMostViewController() -> UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.topMostViewController()
        }
        else if let tabBarController = self as? UITabBarController {
            if let selectedViewController = tabBarController.selectedViewController {
                return selectedViewController.topMostViewController()
            }
            return tabBarController.topMostViewController()
        }
            
        else if let presentedViewController = self.presentedViewController {
            return presentedViewController.topMostViewController()
        }
        
        else {
            return self
        }
    }
}

Usage:

Lorsque vous obtenez topMostViewController sans instance de UIViewController

guard let viewController = UIViewController.topMostViewController() else { return }
print(viewController)

Lorsque vous obtenez topMostViewController de l'instance de UIViewController

let yourVC = UIViewController()
guard let vc = yourVC.topMostViewController() else { return }
print(vc)

1

Où avez-vous mis le code?

J'ai essayé votre code dans ma démo, j'ai découvert, si vous mettez le code

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

échouera, car la fenêtre de clé a encore été définie.

Mais j'ai mis votre code dans un contrôleur de vue

override func viewDidLoad() {

Cela fonctionne juste.


Ce n'est pas dedans didFinishLaunchingWithOptions. J'en ai juste besoin pour diverses raisons de débogage.
Zoyt

1

Dans un cas très rare, avec un segue personnalisé, le contrôleur de vue le plus haut n'est pas dans une pile de navigation ou un contrôleur de barre d'onglets ou présenté, mais sa vue est insérée en haut des sous-vues de la fenêtre principale.

Dans une telle situation, il est nécessaire de vérifier UIApplication.shared.keyWindow.subviews.last == self.viewsi le contrôleur de vue actuel est le plus haut.


1

Pour tous ceux qui recherchent une solution swift 5 / iOS 13+ ( keywindowobsolète depuis iOS 13)

extension UIApplication {

    class func getTopMostViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        } else {
            return nil
        }
    }
}

Comment l'utiliserais-je?
Chris Comas le

Appelez ça comme ça. UIApplication.getTopMostViewController()à l'intérieur de votre ViewController. @ChrisComas
Virendra

1

iOS13 + // top Contrôleur de vue la plus élevée

extension UIViewController {
    func topMostViewController() -> UIViewController {
        if self.presentedViewController == nil {
            return self
        }
        if let navigation = self.presentedViewController as? UINavigationController {
            return navigation.visibleViewController!.topMostViewController()
        }
        if let tab = self.presentedViewController as? UITabBarController {
            if let selectedTab = tab.selectedViewController {
                return selectedTab.topMostViewController()
            }
            return tab.topMostViewController()
        }
        return self.presentedViewController!.topMostViewController()
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return UIWindow.key!.rootViewController?.topMostViewController()
    }
}

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

//use let vc = UIApplication.shared.topMostViewController()

// End top Most view Controller

0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }

0

La meilleure solution pour moi est une extension avec une fonction. Créez un fichier rapide avec cette extension

Le premier est l'extension UIWindow :

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

à l'intérieur de cette fonction d'ajout de fichier

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

Et si vous souhaitez l'utiliser, vous pouvez l'appeler n'importe où. Exemple :

  override func viewDidLoad() {
    super.viewDidLoad()
      if let topVC = visibleViewController() {
             //show some label or text field 
    }
}

Le code de fichier est comme ceci :

import UIKit

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

0
extension UIViewController {
    func topMostViewController() -> UIViewController {
        if self.presentedViewController == nil {
            return self
        }
        if let navigation = self.presentedViewController as? UINavigationController {
            return navigation.visibleViewController.topMostViewController()
        }
        if let tab = self.presentedViewController as? UITabBarController {
            if let selectedTab = tab.selectedViewController {
                return selectedTab.topMostViewController()
            }
            return tab.topMostViewController()
        }
        return self.presentedViewController!.topMostViewController()
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

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

0

dans SWIFT 5.2

vous pouvez utiliser sous le code:

import UIKit

extension UIWindow {
    static func getTopViewController() -> UIViewController? {
        if #available(iOS 13, *){
            let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
            
            if var topController = keyWindow?.rootViewController {
                while let presentedViewController = topController.presentedViewController {
                    topController = presentedViewController
                }
                return topController
            }
        } else {
            if var topController = UIApplication.shared.keyWindow?.rootViewController {
                while let presentedViewController = topController.presentedViewController {
                    topController = presentedViewController
                }
                return topController
            }
        }
        return nil
    }
}

Il ne s'agit pas de la version Swift, mais de la version UIKit.
Blazej SLEBODA
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.