Come trovare il controller di visualizzazione più in alto su iOS


265

Mi sono imbattuto in un paio di casi in cui sarebbe conveniente essere in grado di trovare il controller di visualizzazione "più in alto" (quello responsabile della visualizzazione corrente), ma non ho trovato un modo per farlo.

Fondamentalmente la sfida è questa: dato che si sta eseguendo in una classe che non è un controller di visualizzazione (o una visualizzazione) [e non ha l'indirizzo di una visualizzazione attiva] e non è stato passato l'indirizzo del controller di visualizzazione più in alto ( o, diciamo, l'indirizzo del controller di navigazione), è possibile trovare quel controller di visualizzazione? (E, se sì, come?)

Oppure, in caso contrario, è possibile trovare la vista dall'alto?


Quindi stai dicendo che non è possibile.
Hot Licks

@Daniel no, sto dicendo che sembra che il tuo codice potrebbe utilizzare un po 'di riprogettazione, perché raramente dovresti aver bisogno di saperlo. Inoltre, l'idea di "primo piano" è valida solo in determinati contesti, e anche in questo caso non sempre.
Dave DeLong

@Daniel avevo letto male la tua domanda. Ci sono molti se e ma che cercano di rispondere a questo. Dipende dal flusso del controller della vista. La risposta di @ Wilbur dovrebbe essere un buon punto di partenza per rintracciarlo.
Deepak Danduprolu

Bene, semplificiamolo in un caso specifico. Se volessi scrivere un clone di UIAlertView, come lo farei? Si noti che può funzionare correttamente senza che venga trasmessa alcuna indirizzabilità ad altri controller o viste.
Hot Licks

4
@Daniel: l'aggiunta di una seconda UIWindow funziona bene per le sovrapposizioni simili alla visualizzazione degli avvisi.
Wilbur Vandrsmith,

Risposte:


80

iOS 4 ha introdotto la proprietà rootViewController su UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

Tuttavia, dovrai impostarlo da solo dopo aver creato il controller di visualizzazione.


158
Wilbur, questo ti darà l'opposto di ciò che l'operazione ha chiesto. rootViewController è il controller di visualizzazione di base piuttosto che il più alto.
m4rkk

3
m4rkk: "Il più alto" dipende dalla direzione da cui stai guardando. I nuovi controller vengono aggiunti in alto (simile a uno stack) o in basso (ad albero)? In ogni caso, l'OP ha menzionato il controller di navigazione come in alto, il che implica la vista verso il basso.
Wilbur Vandrsmith

51
La parola "top" è usata per il controller della vista, che è visivamente in alto (come -[UINavigationController topViewController]). Poi c'è la parola "root", che è la radice dell'albero (come -[UIWindow rootViewController].
Tricertops

13
@ImpurestClub non riesco a trovarlo nella documentazione , non sembra che Xcode lo trovi.
Drux

4
@adib no, appartiene a UINavigationController
David H

437

Penso che tu abbia bisogno di una combinazione della risposta accettata e di @ fishstix

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

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

    return topController;
}

Swift 3.0 e versioni successive

func topMostController() -> UIViewController? {
    guard let window = UIApplication.shared.keyWindow, let rootViewController = window.rootViewController else {
        return nil
    }

    var topController = rootViewController

    while let newTopController = topController.presentedViewController {
        topController = newTopController
    }

    return topController
}

4
Inoltre, puoi controllare UINavigationControllere chiedere il suo topViewControllero anche controllare UITabBarControllere chiedere selectedViewController. In questo modo otterrai il controller di visualizzazione attualmente visibile all'utente.
Tricertops

34
Questa è una soluzione incompleta, poiché attraversa solo la gerarchia dei controller di visualizzazione presentati in modo modale, non la gerarchia di childViewControllers (come quella usata da UINavigationController, UITabBarController, ecc.).
Algal

3
Questo è un ottimo modo per astrarre la presentazione di un controller di visualizzazione modale che riprende allo stato corrente dell'applicazione, nel mio caso era una schermata di rientro della password dopo il timeout dell'applicazione. Grazie!
erversteeg

12
@algal: non proprio: UITabBarController, UINavigationController sono già i controller di visualizzazione più in alto nella gerarchia. A seconda di cosa vuoi fare con il "controller più in alto" potresti non volerli attraversare affatto e giocherellare con il loro contenuto. Nel mio caso si trattava di presentare un controller modale in cima a tutto, e per questo ho bisogno di ottenere l'UINaviationController o UITabBarController, non il loro contenuto !!
Rick77

1
@ Rick77, se questo è vero, il tuo piccolo commento sepolto qui sotto rende superflue le tonnellate di complicate modifiche nelle altre risposte. Poiché nessun altro lo menziona affatto, sento di doverti chiedere di affermare che è vero. E se lo è, è così importante che merita di essere una risposta tutta sua. Perché la grande maggioranza delle altre risposte fa salti mortali cercando di risolvere questo problema. Salveresti vite!
Le Mot Juiced

150

Per completare la risposta di JonasG (che ha omesso i controller della barra delle schede durante l'attraversamento), ecco la mia versione di restituzione del controller di visualizzazione attualmente visibile:

- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}

2
Bello, sì, mi sono dimenticato dei controller TabBar: P
JonasG

9
Non includechildViewControllers
Fantastico-o

Guarda la mia risposta di seguito che migliora la risposta precedente gestendo i casi che @kleo ha lasciato fuori come i popover, i controller di visualizzazione aggiunti come sottoviste ad altri controller di visualizzazione durante l'attraversamento
Rajesh

Se stai usando return [self topViewControllerWithRootViewController: navigationController.visibleViewController] ;, lo stesso visibleViewController restituisce il controller di visualizzazione presentato (SE ANY), anche se è un UIAlertController. Per qualcuno che ha bisogno di evitare il controller degli avvisi dell'interfaccia utente, utilizzare topViewController invece di visibleViewController
Johnykutty

Solo per aggiungere i miei 50 centesimi a questo - stavo lottando per farlo funzionare nel mio viewcontroller che carica un webView .. il motivo per cui non riuscivo a farlo funzionare era perché la vista non era ancora pronta (non ha terminato il caricamento) e quindi non era visibile. Ciò ha portato a una situazione in cui l'acquisizione di un topViewContoller non riusciva, perché UINavigationController stava cercando di ottenere un ViewController visibile mentre non c'era ancora un ViewController visibile. Quindi, se qualcuno affronta questo problema, assicurati che la visualizzazione finisca di caricarsi prima di effettuare una chiamata al metodo topViewController sopra.
mbuster

56

Una versione completa non ricorsiva, che si prende cura di diversi scenari:

  • Il controller della vista presenta un'altra vista
  • Il controller di visualizzazione è un file UINavigationController
  • Il controller di visualizzazione è un file UITabBarController

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

2
L'ho chiamato visibleViewControllerper chiarire cosa fa.
Jonny

31

Ottenere la maggior parte del controller di visualizzazione per Swift utilizzando le estensioni

Codice:

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

Utilizzo:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()

eccellente - grazie mille per questa soluzione. Il trucco delle subviews era necessario! Di nuovo, molte grazie, mi hai salvato la giornata.
iKK

27

Per completare la risposta di Eric (che ha escluso i popover, i controller di navigazione, i controller di tabbarcazione, i controller di visualizzazione aggiunti come sottoviste ad alcuni altri controller di visualizzazione durante l'attraversamento), ecco la mia versione di restituzione del controller di visualizzazione attualmente visibile:

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

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

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

E ora tutto ciò che devi fare per ottenere il miglior controller di visualizzazione è chiamare il metodo sopra come segue:

UIViewController *topMostViewControllerObj = [self topViewController];

Manca anche SplitViewController?
apinho

21

Questa risposta include childViewControllerse mantiene un'implementazione pulita e leggibile.

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

Aggiornato un po 'di codice, anche perché mostra quale controller è minimizzandolo e ripristinandolo di nuovo. nik-kov-ios-developer.blogspot.ru/2016/12/…
Nik Kov

Ehi, andiamo, dov'è il tuo "topVisibleViewController"?
Paradise

12

Recentemente ho riscontrato questa situazione in un mio progetto, che richiedeva di visualizzare una vista di notifica qualunque fosse il controller visualizzato e qualunque fosse il tipo (UINavigationController, controller classico o controller di visualizzazione personalizzato), quando lo stato della rete cambia.

Quindi ho appena rilasciato il mio codice, che è abbastanza semplice e in realtà basato su un protocollo in modo che sia flessibile con ogni tipo di controller del contenitore. Sembra essere correlato alle ultime risposte, ma in modo molto flessibile.

Puoi prendere il codice qui: PPTopMostController

E ho ottenuto il miglior controller che utilizza

UIViewController *c = [UIViewController topMostController];

10

Questo è un miglioramento della risposta di 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) è una funzione di supporto.

Ora tutto ciò che devi fare è chiamare topMostController()e il più alto UIViewController dovrebbe essere restituito!


7
Dal 1983 direi. Ricorda che Objective-C contiene C ... Il wrapping del codice ObjC nelle funzioni C è una pratica comune, quindi sì, questo è codice Objective-C.
JonasG

@ JonasG Ciao Jonas, in quali circostanze preferisci racchiudere il codice ObjC in C? Perché a volte vedo funzioni C come questa e non riesco a distinguere l'utilizzo. Il wrapping del codice in C offre vantaggi in termini di prestazioni?
OzBoz

1
@OzBoz In situazioni in cui non è immediatamente chiaro a quale classe selfdovrebbe appartenere.
adib

9

Per l'ultima versione di Swift:
crea un file, assegnagli un nome UIWindowExtension.swifte incolla il seguente frammento:

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
}

Usalo ovunque come:

if let topVC = getTopViewController() {

}

Non voglio cambiare troppo la tua risposta, ma suggerirei alcune cose. 1. Aggiungere il supporto per UISplitViewController. 2. usa switchinvece di if else. 3. Non sono sicuro di aver bisogno anche di una funzione statica, penso che potresti farlo facilmente nel primo livello di istanza var che hai dichiarato. 4. Probabilmente è meglio non creare troppe funzioni globali, ma è una questione di gusti. È possibile utilizzare una riga di codice per ottenere l'effetto della funzione globale:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Jordan Smith

9

Usa l'estensione sotto per afferrare la corrente visibile UIViewController. Ha funzionato per Swift 4.0 e versioni successive

Swift 4.0 e versioni successive:

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

Come usare?

let objViewcontroller = UIApplication.topViewController()

Questo test non dovrebbe presentedViewControllerprima, prima dei casi UINavigationControllere UITabBarController? In caso contrario, se un controller di visualizzazione viene presentato in modo modale da un UINavigationControllero UITabBarController, non verrà restituito come controller di visualizzazione superiore, anche se è il controller di visualizzazione visibile.
Drew

8
@implementation UIWindow (estensioni)

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

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

    return topController;
}

@fine

Non credo tu abbia soddisfatto la condizione indicata nel post originale.
Hot Licks

8

Ecco la mia opinione su questo. Grazie a @Stakenborg per aver indicato come evitare di ottenere UIAlertView come il controller più in alto

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

Dovresti evitare di denominare metodi come getSomething:in Objective-C. Questo ha un significato speciale (altro: cocoadevcentral.com/articles/000082.php ) e non soddisfi questi requisiti nel tuo codice.
Vive

8

Semplice estensione per UIApplicationin Swift:

NOTA:

Ci tiene moreNavigationControllerdentroUITabBarController

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

Utilizzo semplice:

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

SÌ SÌ SÌ - Ci sono molte soluzioni in giro per il web per trovare il topMostViewController ma se la tua app ha una barra delle schede con una scheda Altro, DEVI gestirla in modo leggermente diverso.
Andy Obusek

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

L'ho usato, ma nota che si rompe quando è presente più di un controller di visualizzazione
Chuck Boris,

5

Una soluzione concisa ma completa in Swift 4.2, tiene conto di UINavigationControllers , UITabBarControllers , controller presentati e child view:

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

Utilizzo:

let viewController = UIApplication.shared.topmostViewController()

4

Ancora un'altra soluzione Swift

func topController() -> UIViewController? {

    // recursive follow
    func follow(from:UIViewController?) -> UIViewController? {
        if let to = (from as? UITabBarController)?.selectedViewController {
            return follow(to)
        } else if let to = (from as? UINavigationController)?.visibleViewController {
            return follow(to)
        } else if let to = from?.presentedViewController {
            return follow(to)
        }
        return from
    }

    let root = UIApplication.sharedApplication().keyWindow?.rootViewController

    return follow(root)

}

4

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

Usalo da qualsiasi luogo come,

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

o come,

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

Adatta a qualsiasi classe come UINavigationController, UITabBarController

Godere!


1
@BilalBakhrom dice "Upvoted. Penso che la tua risposta sia la migliore. Non puoi chiamare direttamente il metodo topViewController (). La classe UIApplication è singleton, usa un'istanza chiamata" shared "." in una modifica che ho votato per rifiutare. Se questo è effettivamente corretto, fare clic qui per accedere a un menu in cui è possibile approvare la modifica.
wizzwizz4

3

Ecco cosa ha funzionato per me.

Ho scoperto che a volte il controller era nullo nella finestra della chiave, poiché la keyWindow è una cosa del sistema operativo come un avviso, ecc.

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

     if (topController == nil)
     {
         // The windows in the array are ordered from back to front by window level; thus,
         // the last window in the array is on top of all other app windows.
         for (UIWindow *aWndow in [[UIApplication sharedApplication].windows reverseObjectEnumerator])
         {
             topController = aWndow.rootViewController;
             if (topController)
                 break;
         }
     }

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

     return topController;
 }

3

Espandendo la risposta di @ Eric, devi stare attento che la keyWindow sia effettivamente la finestra che desideri. Se stai tentando di utilizzare questo metodo dopo aver toccato qualcosa in una visualizzazione di avviso, ad esempio, la finestra chiave sarà effettivamente la finestra di avviso e ciò causerà senza dubbio problemi. Questo mi è accaduto in natura durante la gestione dei link diretti tramite un avviso e ha causato SIGABRT senza STACK TRACE. Puttana totale da eseguire il debug.

Ecco il codice che sto usando ora:

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

Sentiti libero di mescolare questo con qualsiasi sapore di recupero del controller della vista dall'alto che ti piace dalle altre risposte su questa domanda.


Hai trovato che questa sia una soluzione completa? Molte delle altre risposte sono estremamente complicate e cercano di spiegare così tanti casi limite. Io voglio che questo è vero, è così semplice ed elegante.
Le Mot Juiced

Non ho mai avuto problemi con esso. Se non stai facendo nulla di insolito con il tuo stack di navigazione, questo dovrebbe funzionare, altrimenti alcune delle altre soluzioni gestiscono casi più complicati.
Stakenborg

3

Soluzione Swift alternativa:

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

    return topController!
}

3

Questa soluzione è la più completa. Prende in considerazione: UINavigationController UIPageViewController UITabBarController E il controller di visualizzazione più in alto presentato dal controller di visualizzazione dall'alto

L'esempio è in Swift 3.

Ci sono 3 sovraccarichi

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

2

Ottima soluzione in Swift, implementabile in 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
}

2

So che è molto tardi e potrebbe essere ridondante. Ma di seguito è riportato lo snippet che mi è venuto in mente che funziona per me:

    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
    }

1

Penso che la maggior parte delle risposte siano state completamente ignorate UINavigationViewController, quindi ho gestito questo caso d'uso con la seguente implementazione.

+ (UIViewController *)topMostController {
    UIViewController * topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topController.presentedViewController || [topController isMemberOfClass:[UINavigationController class]]) {
        if([topController isMemberOfClass:[UINavigationController class]]) {
            topController = [topController childViewControllers].lastObject;
        } else {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}

1

Molte di queste risposte sono incomplete. Sebbene questo sia in Objective-C, questa è la migliore raccolta di tutti che potrei mettere insieme per ora, come blocco non ricorsivo:

UIViewController *(^topmostViewControllerForFrontmostNormalLevelWindow)(void) = ^UIViewController *{
    // NOTE: Adapted from various stray answers here:
    //   /programming/6131205/iphone-how-to-find-topmost-view-controller/20515681

    UIViewController *viewController;

    for (UIWindow *window in UIApplication.sharedApplication.windows.reverseObjectEnumerator.allObjects) {
        if (window.windowLevel == UIWindowLevelNormal) {
            viewController = window.rootViewController;
            break;
        }
    }

    while (viewController != nil) {
        if ([viewController isKindOfClass:[UITabBarController class]]) {
            viewController = ((UITabBarController *)viewController).selectedViewController;
        } else if ([viewController isKindOfClass:[UINavigationController class]]) {
            viewController = ((UINavigationController *)viewController).visibleViewController;
        } else if (viewController.presentedViewController != nil && !viewController.presentedViewController.isBeingDismissed) {
            viewController = viewController.presentedViewController;
        } else if (viewController.childViewControllers.count > 0) {
            viewController = viewController.childViewControllers.lastObject;
        } else {
            BOOL repeat = NO;

            for (UIView *view in viewController.view.subviews.reverseObjectEnumerator.allObjects) {
                if ([view.nextResponder isKindOfClass:[UIViewController class]]) {
                    viewController = (UIViewController *)view.nextResponder;

                    repeat = YES;
                    break;
                }
            }

            if (!repeat) {
                break;
            }
        }
    }

    return viewController;
};

0

Funziona alla grande per trovare il viewController 1 dall'alto da qualsiasi controlle di visualizzazione root

+ (UIViewController *)topViewControllerFor:(UIViewController *)viewController
{
    if(!viewController.presentedViewController)
        return viewController;
    return [MF5AppDelegate topViewControllerFor:viewController.presentedViewController];
}

/* View Controller for Visible View */

AppDelegate *app = [UIApplication sharedApplication].delegate;
UIViewController *visibleViewController = [AppDelegate topViewControllerFor:app.window.rootViewController]; 

0

Non sono sicuro che questo aiuterà ciò che stai cercando di ottenere trovando il controller di visualizzazione più in alto, ma stavo cercando di presentare un nuovo controller di visualizzazione, ma se il mio controller di visualizzazione principale avesse già una finestra di dialogo modale, sarebbe bloccato, quindi ho passerebbe all'inizio di tutti i controller di visualizzazione modale utilizzando questo codice:

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

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

0

potresti trovare il controller di visualizzazione più in alto usando

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];

Tranne che, se leggi effettivamente la domanda, selfnon ha navigationControllerproprietà.
Hot Licks

0

Un'altra soluzione si basa sulla catena del responder, che può o non può funzionare a seconda di quale sia il primo responder:

  1. Ottieni il primo soccorritore .
  2. Ottieni l'UIViewController associato a quel primo risponditore .

Pseudo codice di esempio:

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