Ottieni il massimo da UIViewController


192

Non riesco a ottenere il massimo UIViewControllersenza accedere a UINavigationController. Ecco quello che ho finora:

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

Tuttavia, non sembra fare nulla. L' keyWindowe rootViewControllersembrano essere valori non nil troppo, in modo che il concatenamento opzionale non dovrebbe essere un problema.

NOTA: è una cattiva idea fare qualcosa del genere. Rompe il modello MVC.


Qui è una soluzione alternativa disponibile stackoverflow.com/a/39994115/1872233
iDevAmit

Risposte:


287

presentViewControllermostra un controller di visualizzazione. Non restituisce un controller di visualizzazione. Se non stai usando a UINavigationController, probabilmente stai cercando presentedViewControllere dovrai iniziare dalla radice e scorrere le viste presentate.

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

    // topController should now be your topmost view controller
}

Per Swift 3+:

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

    // topController should now be your topmost view controller
}

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

1
Qualcuno può spiegare il ciclo while? A me sembra che non ci sia nulla su cui fare un giro; Non sono nemmeno sicuro del perché questo compili.
Professor Tom

15
@ProfessorTom Il ciclo continua fintanto che topController.presentedViewControllerrestituisce qualcosa (ovvero, il controller ha un controller figlio presentato). È while letper far rispettare il fatto che topController.presentedViewControllerdeve restituire qualcosa. Se restituisce zero (ovvero, questo controller non ha figli presentati), smetterà di eseguire il looping. Nel corpo del loop, riassegna il bambino come corrente topControllere si riavvia di nuovo, scendendo nella gerarchia del controller di visualizzazione. Può riassegnare in topControllerquanto è una dichiarazione varesterna if.
Rickick

1
grazie. Non sono stato in grado di trovare alcun esempio online di while let. Ci sono, ovviamente, molti if letesempi da trovare.
Professor Tom

1
La let x = somethingThatCouldBeNilsintassi è un trucco molto utile da usare ovunque si possa usare un valore / condizione di verità. Se non lo utilizzassimo qui, dovremmo assegnare esplicitamente un valore, quindi testare per vedere se è effettivamente lì. Penso che sia davvero succinto ed espressivo.
Rickick

1
Ho familiarità con il trucco, è solo un po 'più difficile ragionare nei loop while - per i quali ho trovato una carenza di esempi - specialmente questo.
Professor Tom

272

avere questa estensione

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

Puoi usarlo ovunque sul tuo controller

if let topController = UIApplication.topViewController() {

}

1
Grazie per il suggerimento sull'estensione :)
Thein

4
Ho tentato di apportare una modifica importante a questa risposta, ma è stata respinta (non ho idea del perché e le ragioni del modello fornite non abbiano senso): è importante verificare se nav.visibleViewController è nullo prima di usarlo nel ricorsivo call (proprio come viene controllato tab.selectedViewController) perché altrimenti, se fosse zero, entreresti in un ciclo infinito ricorsivo.
Ethan G,

@EthanG Secondo la mia comprensione, se nav.visibleViewController è zero, la funzione restituirà zero (passa all'ultimo return). Come può entrare in un ciclo infinito?
Desmond DAI,

3
Penso che sarebbe più logico renderlo una funzione statica di UIViewController
Leszek Zarna,

1
Il controllo 'presentViewController' dovrebbe probabilmente venire prima se vuoi catturare controller di visualizzazione presentati in modo modale su UITabBarControllers ..
Tokuriku

65

Per un rapido 4/5 + per ottenere il massimo controllo del controller

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

Come usare

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

2
Soluzione brillante. Grazie!
Andrey M.,

2
'keyWindow' è stato deprecato in iOS 13.0.
RS7,

2
'keyWindow' stata sconsigliata a 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
            }
        }
    }
}

Uso:

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

questa soluzione sembrava davvero promettente, tuttavia ho cercato di eseguirla per ottenere il controller di visualizzazione in cui mi trovo quando ricevo una notifica push e ha generato un errore nullo sulreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike

@Mike devi usare solo presentatoViewController, non presentatoViewController. presentatoViewController
allaire il

@allaire Se hai presentato un controller di visualizzazione modale sopra un controller di visualizzazione modale, allora hai bisogno .presentedViewController.presentedViewControllero no?
Baran Emre,

6

Basato sulla risposta di Dianz, la versione 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;
}

Non funzionerà per UINavigationController in UITabBarController. restituirà UINavigationController, dovrebbe restituire il topController in navigazione bloccato.
Mike.R,


6

Ho adorato la risposta di @Dianz , quindi ecco la versione di Swift 3. Fondamentalmente è la stessa cosa, ma mancava una parentesi graffa e alcuni nomi di sintassi / variabili / metodo sono cambiati. Quindi eccolo qui!

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'utilizzo è comunque lo stesso esatto:

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

6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 Ho fatto alcuni test sulle risposte e i commenti su questo sito. Per me, il seguente funziona

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

Quindi, ottieni la vista superiore Controllo da:

UIApplication.shared.topMostViewController()

5

Usa questo codice per trovare la maggior parte di UIViewController

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

2
In che modo differisce dalla risposta di Ricky?
ElectroBuddha,

5

Leggera variazione su @AlberZou utilizzando una variabile calcolata anziché una funzione

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

Poi dici

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

4

Basato su Bob -c sopra:

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

Troppi sapori ma nessuno elaborato iterativo. Combinato dai precedenti:

     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
    }

2

puoi definire una variabile UIViewController in AppDelegate e in ogni viewWillAppear impostare la variabile su self (tuttavia la risposta dianz è la risposta migliore).

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

1
grazie mille funziona bene per me come l'altra soluzione quando cerca di ottenere la navigazione. Controlla che ritorni a zero quindi non sono stato in grado di spingere alcun nuovo vc
Amr Angry

Assicurati che l'attuale CV sia definito come riferimento debole, altrimenti avrai una perdita di memoria.
bubuxu,

2

Per trovare la viewController visibile in Swift 3

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

     let prefs = UserDefaults.standard

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

     }
}

Questo codice trova l'ultimo aggiunto o l'ultimo controller attivo visibile.

Questo l'ho usato in AppDelegate per trovare Controller vista attiva


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

Uso ambiguo di "visibleViewController"
Omar N Shamali,

1

Dove hai inserito il codice?

Provo il tuo codice nella mia demo, ho scoperto, se hai inserito il codice

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

fallirà, perché la finestra chiave è stata ancora impostata.

Ma ho messo il tuo codice in alcuni controller di visualizzazione

override func viewDidLoad() {

Funziona e basta.


Non è dentro didFinishLaunchingWithOptions. Ho solo bisogno di questo per vari scopi di debug.
Zoyt,

1

In un caso molto raro, con segue personalizzato, il controller più in alto nella vista non si trova in uno stack di navigazione o in un controller della barra delle schede o non viene presentato, ma la sua vista viene inserita nella parte superiore delle visualizzazioni secondarie del keyown.

In tale situazione, è necessario verificare se UIApplication.shared.keyWindow.subviews.last == self.viewper determinare se il controller di visualizzazione corrente è il massimo.


1

Per tutti coloro che cercano una soluzione rapida 5 / iOS 13+ ( keywindowè obsoleta da 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
        }
    }
}

Come lo userei?
Chris Comas,

Chiamalo così. UIApplication.getTopMostViewController()all'interno del ViewController. @ChrisComas
Virendra,

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 migliore soluzione per me è un'estensione con una funzione. Crea un file rapido con questa estensione

La prima è l'estensione 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
            }
        }
    }
}

all'interno di quel file aggiungi funzione

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

E se vuoi usarlo, puoi chiamarlo ovunque. Esempio :

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

Il codice file è così :

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.