So finden Sie den obersten Ansichts-Controller unter iOS

253

Ich bin jetzt auf einige Fälle gestoßen, in denen es praktisch wäre, den "obersten" Ansichts-Controller (den für die aktuelle Ansicht verantwortlichen) zu finden, aber keinen Weg gefunden, dies zu tun.

Grundsätzlich ist die Herausforderung folgende: Vorausgesetzt, man führt in einer Klasse aus, die kein View Controller (oder eine View) ist. [und nicht die Adresse einer aktiven Ansicht hat] und der die Adresse des obersten Ansichtscontrollers nicht übergeben wurde ( oder beispielsweise die Adresse des Navigationscontrollers), ist es möglich, diesen Ansichtscontroller zu finden? (Und wenn ja, wie?)

Oder ist es sonst möglich, die oberste Ansicht zu finden?

Hot Licks
quelle
Sie sagen also, es ist nicht möglich.
Hot Licks
@ Daniel nein, ich sage, es scheint, als könnte Ihr Code neu gestaltet werden, da Sie dies selten wissen müssen. Auch die Idee des "obersten" ist nur in bestimmten Kontexten gültig, und selbst dann nicht immer.
Dave DeLong
@ Daniel Ich hatte deine Frage falsch verstanden. Es gibt viele Wenn und Aber, die versuchen, diese Frage zu beantworten. Dies hängt von Ihrem View Controller-Ablauf ab. @ Wilburs Antwort sollte ein guter Ausgangspunkt sein, um sie aufzuspüren.
Deepak Danduprolu
Vereinfachen wir es auf einen bestimmten Fall. Wenn ich einen Klon von UIAlertView schreiben wollte, wie würde ich das machen? Beachten Sie, dass es einwandfrei funktionieren kann, ohne dass eine Adressierbarkeit an andere Controller oder Ansichten übergeben wird.
Hot Licks
4
@ Daniel: Das Hinzufügen eines zweiten UIWindow eignet sich gut für Überlagerungen in Form von Warnmeldungen.
Wilbur Vandrsmith

Antworten:

75

iOS 4 hat die rootViewController-Eigenschaft in UIWindow eingeführt:

[UIApplication sharedApplication].keyWindow.rootViewController;

Sie müssen es jedoch selbst einstellen, nachdem Sie den Ansichts-Controller erstellt haben.

Wilbur Vandrsmith
quelle
155
Wilbur, das gibt dir das Gegenteil von dem, was die Operation verlangt hat. rootViewController ist eher der Basisansichts-Controller als der oberste.
m4rkk
3
m4rkk: "Top-most" hängt davon ab, aus welcher Richtung Sie schauen. Werden neue Controller oben (stapelartig) oder unten (baumartig) hinzugefügt? In jedem Fall erwähnte das OP, dass sich der Navigationscontroller oben befindet, was impliziert, dass die Ansicht nach unten wächst.
Wilbur Vandrsmith
50
Das Wort „top“ wird für den View Controller verwendet, dh oben (wie -[UINavigationController topViewController]). Dann gibt es das Wort „Wurzel“, das die Wurzel des Baumes ist (wie -[UIWindow rootViewController].
Tricertops
13
@ImpurestClub Ich kann es nicht in der Dokumentation finden , Xcode scheint es nicht zu finden.
Drux
4
@adib nein, es gehört zu UINavigationController
David H
428

Ich denke, Sie brauchen eine Kombination aus der akzeptierten Antwort und @ fishstix

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

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

    return topController;
}

Swift 3.0+

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

    var topController = rootViewController

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

    return topController
}
Eric
quelle
4
Darüber hinaus können Sie nachsehen UINavigationControllerund danach fragen topViewControlleroder sogar nachsehen UITabBarControllerund fragen selectedViewController. Dadurch erhalten Sie den View Controller, der derzeit für den Benutzer sichtbar ist.
Tricertops
33
Dies ist eine unvollständige Lösung, da nur die Hierarchie der modal dargestellten Ansichtscontroller durchlaufen wird, nicht die Hierarchie der childViewController (wie sie von UINavigationController, UITabBarController usw. verwendet werden).
Algen
3
Dies ist eine großartige Möglichkeit, die Darstellung eines Modal View Controllers zu abstrahieren, der auf den aktuellen Anwendungsstatus zurückgesetzt wird. In meinem Fall war dies ein Bildschirm zum erneuten Eingeben von Kennwörtern, nachdem das Zeitlimit für die Anwendung abgelaufen war. Vielen Dank!
Erversteeg
11
@algal: nicht wirklich: UITabBarController, UINavigationController sind bereits die obersten Ansichtscontroller in der Hierarchie. Je nachdem, was Sie mit dem "obersten Controller" tun möchten, möchten Sie sie möglicherweise überhaupt nicht durchlaufen und mit ihrem Inhalt herumspielen. In meinem Fall war es, einen modalen Controller über alles zu stellen, und dafür brauche ich den UINaviationController oder UITabBarController, nicht deren Inhalt !!
Rick77
1
@ Rick77, wenn das stimmt, macht dein einziger kleiner Kommentar, der hier unten vergraben ist, die Unmengen komplizierter Änderungen in den anderen Antworten unnötig. Da dies sonst niemand erwähnt, muss ich Sie bitten, zu bestätigen, dass es wahr ist. Und wenn ja, ist es so wichtig, dass es eine eigene Antwort verdient. Weil die große Mehrheit der anderen Antworten Backflips macht, um dieses Problem zu beheben. Sie würden Leben retten!
Le Mot Juiced
150

Um die Antwort von JonasG zu vervollständigen (der beim Durchlaufen die Registerkarten-Controller ausgelassen hat), ist hier meine Version der Rückgabe des aktuell sichtbaren Ansichts-Controllers:

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

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}
kleo
quelle
2
Schön, ja, ich habe TabBar-Controller vergessen: P
JonasG
9
Nicht enthaltenchildViewControllers
Awesome-o
Schauen Sie sich meine Antwort unten an, die die oben genannte Antwort verbessert, indem Sie die Fälle behandeln, die @kleo ausgelassen hat, wie z. B. Popover, Ansichts-Controller, die beim Durchlaufen einigen anderen Ansichts-Controllern als Unteransichten hinzugefügt wurden
Rajesh,
Wenn Sie return [self topViewControllerWithRootViewController: navigationController.visibleViewController] verwenden, gibt visualViewController selbst den präsentierten View Controller (IF ANY) zurück, auch wenn es sich um einen UIAlertController handelt. Für jemanden, der UI Alert Controller vermeiden muss, verwenden Sie topViewController anstelle von sichtbarem ViewController
Johnykutty
Nur um meine 50 Cent hinzuzufügen - ich hatte Probleme, dies in meinem Viewcontroller zum Laden einer WebView zum Laufen zu bringen. Der Grund, warum ich diese Funktion nicht zum Laufen bringen konnte, war, dass die Ansicht noch nicht fertig war (das Laden nicht beendet wurde). und deshalb war es nicht sichtbar. Dies führte zu einer Situation, in der das Abrufen eines topViewContoller fehlschlug, da der UINavigationController versuchte, einen sichtbaren ViewController abzurufen, während noch kein sichtbarer ViewController vorhanden war. Wenn also jemand mit diesem Problem konfrontiert ist, stellen Sie sicher, dass Ihre Ansicht vollständig geladen ist, bevor Sie die oben genannte topViewController-Methode aufrufen.
mbuster
52

Eine vollständige nicht rekursive Version, die verschiedene Szenarien berücksichtigt:

  • Der Ansichtscontroller zeigt eine andere Ansicht an
  • Der View Controller ist a UINavigationController
  • Der View Controller ist a UITabBarController

Ziel c

 UIViewController *topViewController = self.window.rootViewController;
 while (true)
 {
     if (topViewController.presentedViewController) {
         topViewController = topViewController.presentedViewController;
     } else if ([topViewController isKindOfClass:[UINavigationController class]]) {
         UINavigationController *nav = (UINavigationController *)topViewController;
         topViewController = nav.topViewController;
     } else if ([topViewController isKindOfClass:[UITabBarController class]]) {
         UITabBarController *tab = (UITabBarController *)topViewController;
         topViewController = tab.selectedViewController;
     } else {
         break;
     }
 }

Swift 4+

extension UIWindow {
    func topViewController() -> UIViewController? {
        var top = self.rootViewController
        while true {
            if let presented = top?.presentedViewController {
                top = presented
            } else if let nav = top as? UINavigationController {
                top = nav.visibleViewController
            } else if let tab = top as? UITabBarController {
                top = tab.selectedViewController
            } else {
                break
            }
        }
        return top
    }
}
Yuchen Zhong
quelle
2
Ich habe es benannt visibleViewController, um klar zu machen, was es tut.
Jonny
31

Erhalten des Top View View Controllers für Swift mithilfe von Erweiterungen

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

Verwendung:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()
Varuna
quelle
ausgezeichnet - vielen Dank für diese Lösung. Der Subviews-Trick wurde benötigt! Nochmals vielen Dank, du hast meinen Tag gerettet.
iKK
25

Um Erics Antwort zu vervollständigen (der Popover, Navigations-Controller, Tabbar-Controller, Ansichts-Controller, die während des Durchlaufs als Unteransichten zu einigen anderen Ansichts-Controllern hinzugefügt wurden, weggelassen hat), ist hier meine Version der Rückgabe des aktuell sichtbaren Ansichts-Controllers:

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

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

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

Und jetzt müssen Sie nur noch die obige Methode wie folgt aufrufen, um den Top View Controller zu erhalten:

UIViewController *topMostViewControllerObj = [self topViewController];
Rajesh
quelle
Vermissen Sie auch SplitViewController?
Apinho
21

Diese Antwort beinhaltet childViewControllersund pflegt eine saubere und lesbare Implementierung.

+ (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;
}
Super-o
quelle
Der Code wurde aktualisiert, um zu zeigen, um welchen Controller es sich handelt, indem er minimiert und erneut wiederhergestellt wird. nik-kov-ios-developer.blogspot.ru/2016/12/…
Nik Kov
Hey, komm schon, wo ist dein "topVisibleViewController"?
Paradies
12

Ich habe diese Situation kürzlich in einem meiner Projekte erhalten, in dem eine Benachrichtigungsansicht angezeigt werden musste, unabhängig davon, welcher Controller angezeigt wurde und welcher Typ (UINavigationController, klassischer Controller oder benutzerdefinierter Ansichtscontroller), wenn sich der Netzwerkstatus änderte.

Also habe ich meinen Code veröffentlicht, der recht einfach ist und tatsächlich auf einem Protokoll basiert, so dass er für jede Art von Container-Controller flexibel ist. Es scheint mit den letzten Antworten verbunden zu sein, aber auf sehr flexible Weise.

Sie können den Code hier herunterladen: abrufen PPTopMostController

Und bekam den Top-Controller mit

UIViewController *c = [UIViewController topMostController];
ipodishima
quelle
10

Dies ist eine Verbesserung von Erics Antwort:

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) ist eine Hilfsfunktion.

Jetzt müssen Sie nur noch anrufen topMostController()und der oberste UIViewController sollte zurückgegeben werden!

JonasG
quelle
7
Seit 1983 würde ich sagen. Denken Sie daran, dass Objective-C C enthält ... Das Umschließen von ObjC-Code in C-Funktionen ist eine gängige Praxis. Ja, dies ist Objective-C-Code.
JonasG
@ JonasG Hallo Jonas, unter welchen Umständen bevorzugen Sie es, ObjC-Code in C zu verpacken? Weil ich manchmal solche C-Funktionen sehe und die Verwendung nicht unterscheiden kann. Bietet das Umschließen von Code in C Leistungsvorteile?
OzBoz
1
@OzBoz In Situationen, in denen nicht sofort klar ist, zu welcher Klasse das selfgehören soll.
Adib
8

Hier ist meine Meinung dazu. Vielen Dank an @Stakenborg für den Hinweis, wie Sie es überspringen können, UIAlertView als obersten Controller zu erhalten

-(UIWindow *) returnWindowWithWindowLevelNormal
{
    NSArray *windows = [UIApplication sharedApplication].windows;
    for(UIWindow *topWindow in windows)
    {
        if (topWindow.windowLevel == UIWindowLevelNormal)
            return topWindow;
    }
    return [UIApplication sharedApplication].keyWindow;
}

-(UIViewController *) getTopMostController
{
    UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
    if (topWindow.windowLevel != UIWindowLevelNormal)
    {
        topWindow = [self returnWindowWithWindowLevelNormal];
    }

    UIViewController *topController = topWindow.rootViewController;
    if(topController == nil)
    {
        topWindow = [UIApplication sharedApplication].delegate.window;
        if (topWindow.windowLevel != UIWindowLevelNormal)
        {
            topWindow = [self returnWindowWithWindowLevelNormal];
        }
        topController = topWindow.rootViewController;
    }

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

    if([topController isKindOfClass:[UINavigationController class]])
    {
        UINavigationController *nav = (UINavigationController*)topController;
        topController = [nav.viewControllers lastObject];

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

    return topController;
}
Kamran Khan
quelle
Sie sollten Benennungsmethoden wie getSomething:in Objective-C vermeiden . Dies hat eine besondere Bedeutung (mehr: cocoadevcentral.com/articles/000082.php ) und Sie erfüllen diese Anforderungen in Ihrem Code nicht.
Vive
7
@implementation UIWindow (Erweiterungen)

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

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

    return topController;
}}

@Ende
FishStix
quelle
Ich glaube nicht, dass Sie die im ursprünglichen Beitrag angegebene Bedingung erfüllt haben.
Hot Licks
7
- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}
lifuqing_ios
quelle
Ich habe dies verwendet, aber beachten Sie, dass es kaputt geht, wenn mehr als ein View Controller
Chuck Boris
7

Für die neueste Swift-Version:
Erstellen Sie eine Datei, benennen Sie sie UIWindowExtension.swiftund fügen Sie das folgende Snippet ein:

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
}

Verwenden Sie es überall als:

if let topVC = getTopViewController() {

}
Ashok
quelle
Ich möchte Ihre Antwort nicht zu sehr ändern, würde aber ein paar Dinge vorschlagen. 1. Fügen Sie Unterstützung für UISplitViewController hinzu. 2. Verwenden Sie switchanstelle von if else. 3. Sie sind sich nicht sicher, ob Sie auch eine statische Funktion benötigen. Ich denke, Sie können dies problemlos in der von Ihnen deklarierten Variablen der ersten Instanz tun. 4. Wahrscheinlich am besten, nicht zu viele globale Funktionen zu erstellen, aber das ist Geschmackssache. Sie können eine Codezeile verwenden, um den Effekt der globalen Funktion zu erzielen:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Jordan Smith
7

Einfache Erweiterung für UIApplicationin Swift:

HINWEIS:

Es kümmert sich um moreNavigationControllerinnenUITabBarController

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

Einfache Verwendung:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}
Bartłomiej Semańczyk
quelle
JA JA JA - Es gibt viele Lösungen im Internet, um den topMostViewController zu finden. Wenn Ihre App jedoch eine Registerkartenleiste mit einer Registerkarte "Mehr" hat, MÜSSEN Sie etwas anders damit umgehen.
Andy Obusek
7

Verwenden Sie die folgende Erweiterung, um den sichtbaren Strom zu erfassen UIViewController. Arbeitete für Swift 4.0 und höher

Swift 4.0 und höher:

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

Wie benutzt man?

let objViewcontroller = UIApplication.topViewController()
COVID-19
quelle
Sollte dieser Test nicht presentedViewControllerzuerst vor den UINavigationControllerund UITabBarControllerFällen erfolgen? Andernfalls wird ein Ansichts-Controller, der modal von einem UINavigationControlleroder dargestellt UITabBarControllerwird, nicht als Draufsicht-Controller zurückgegeben, obwohl der Ansichts-Controller sichtbar ist.
Drew
4

Noch eine schnelle Lösung

func topController() -> UIViewController? {

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

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

    return follow(root)

}
Martin Algesten
quelle
4

Swift 4.2-Erweiterung


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

Verwenden Sie es von überall wie,

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

oder wie,

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

Passt zu allen Klassen wie UINavigationController, UITabBarController

Genießen!

Saranjith
quelle
1
@BilalBakhrom sagt "Upvoted. Ich denke, Ihre Antwort ist die beste. Sie können die topViewController () -Methode nicht direkt aufrufen. Die UIApplication-Klasse ist Singleton. Verwenden Sie eine Instanz namens" shared "." in einer Bearbeitung , die ich abgelehnt habe. Wenn dies tatsächlich korrekt ist, klicken
wizzwizz4
3

Hier ist, was für mich funktioniert hat.

Ich stellte fest, dass der Controller manchmal im Schlüsselfenster Null war, da das keyWindow ein Betriebssystem wie eine Warnung usw. ist.

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

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

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

     return topController;
 }
Tom Andersen
quelle
3

Wenn Sie die Antwort von @ Eric erweitern, müssen Sie darauf achten, dass das keyWindow tatsächlich das gewünschte Fenster ist. Wenn Sie versuchen, diese Methode zu verwenden, nachdem Sie beispielsweise in einer Warnungsansicht auf etwas getippt haben, ist das keyWindow tatsächlich das Fenster der Warnung, und dies führt zweifellos zu Problemen für Sie. Dies passierte mir in freier Wildbahn beim Umgang mit Deep Links über einen Alarm und verursachte SIGABRTs ohne NO STACK TRACE. Total Hündin zum Debuggen.

Hier ist der Code, den ich jetzt verwende:

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

Fühlen Sie sich frei, dies mit einer beliebigen Art des Abrufens des von Ihnen gewünschten Draufsicht-Controllers aus den anderen Antworten auf diese Frage zu mischen.

Stakenborg
quelle
Haben Sie festgestellt, dass dies eine Komplettlösung ist? So viele der anderen Antworten sind äußerst kompliziert und versuchen, so viele Randfälle zu erklären. Ich möchte , dass dies wahr ist, es ist so einfach und elegant.
Le Mot Juiced
Ich hatte noch nie ein Problem damit. Wenn Sie mit Ihrem Navigationsstapel nichts Ungewöhnliches tun, sollte dies funktionieren, andernfalls behandeln einige der anderen Lösungen kompliziertere Fälle.
Stakenborg
3

Alternative Swift-Lösung:

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

    return topController!
}
Esqarrouth
quelle
3

Diese Lösung ist die vollständigste. Es berücksichtigt: UINavigationController UIPageViewController UITabBarController Und den am häufigsten präsentierten View Controller vom Top View Controller

Das Beispiel ist in Swift 3.

Es gibt 3 Überladungen

//Get the topmost view controller for the current application.
public func MGGetTopMostViewController() -> UIViewController?  {

    if let currentWindow:UIWindow = UIApplication.shared.keyWindow {
        return MGGetTopMostViewController(fromWindow: currentWindow)
    }

    return nil
}

//Gets the topmost view controller from a specific window.
public func MGGetTopMostViewController(fromWindow window:UIWindow) -> UIViewController? {

    if let rootViewController:UIViewController = window.rootViewController
    {
        return MGGetTopMostViewController(fromViewController:  rootViewController)
    }

    return nil
}


//Gets the topmost view controller starting from a specific UIViewController
//Pass the rootViewController into this to get the apps top most view controller
public func MGGetTopMostViewController(fromViewController viewController:UIViewController) -> UIViewController {

    //UINavigationController
    if let navigationViewController:UINavigationController = viewController as? UINavigationController {
        let viewControllers:[UIViewController] = navigationViewController.viewControllers
        if navigationViewController.viewControllers.count >= 1 {
            return MGGetTopMostViewController(fromViewController: viewControllers[viewControllers.count - 1])
        }
    }

    //UIPageViewController
    if let pageViewController:UIPageViewController = viewController as? UIPageViewController {
        if let viewControllers:[UIViewController] = pageViewController.viewControllers {
            if viewControllers.count >= 1 {
                return MGGetTopMostViewController(fromViewController: viewControllers[0])
            }
        }
    }

    //UITabViewController
    if let tabBarController:UITabBarController = viewController as? UITabBarController {
        if let selectedViewController:UIViewController = tabBarController.selectedViewController {
            return MGGetTopMostViewController(fromViewController: selectedViewController)
        }
    }

    //Lastly, Attempt to get the topmost presented view controller
    var presentedViewController:UIViewController! = viewController.presentedViewController
    var nextPresentedViewController:UIViewController! = presentedViewController?.presentedViewController

    //If there is a presented view controller, get the top most prensentedViewController and return it.
    if presentedViewController != nil {
        while nextPresentedViewController != nil {

            //Set the presented view controller as the next one.
            presentedViewController = nextPresentedViewController

            //Attempt to get the next presented view controller
            nextPresentedViewController = presentedViewController.presentedViewController
        }
        return presentedViewController
    }

    //If there is no topmost presented view controller, return the view controller itself.
    return viewController
}
Marc Renaud
quelle
3

Eine kurze , aber umfassende Lösung in Swift 4.2, berücksichtigt UINavigationControllers , UITabBarControllers , präsentiert und Kind View - Controller:

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

Verwendung:

let viewController = UIApplication.shared.topmostViewController()
nalexn
quelle
2

Tolle Lösung in Swift, in AppDelegate implementieren

func getTopViewController()->UIViewController{
    return topViewControllerWithRootViewController(UIApplication.sharedApplication().keyWindow!.rootViewController!)
}
func topViewControllerWithRootViewController(rootViewController:UIViewController)->UIViewController{
    if rootViewController is UITabBarController{
        let tabBarController = rootViewController as! UITabBarController
        return topViewControllerWithRootViewController(tabBarController.selectedViewController!)
    }
    if rootViewController is UINavigationController{
        let navBarController = rootViewController as! UINavigationController
        return topViewControllerWithRootViewController(navBarController.visibleViewController)
    }
    if let presentedViewController = rootViewController.presentedViewController {
        return topViewControllerWithRootViewController(presentedViewController)
    }
    return rootViewController
}
Edward Novelo
quelle
1

Ich denke, die meisten Antworten wurden vollständig ignoriert UINavigationViewController, daher habe ich diesen Anwendungsfall mit der folgenden Implementierung behandelt.

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

    return topController;
}
Aamir
quelle
1

Ich weiß, dass es sehr spät ist und möglicherweise überflüssig ist. Aber das Folgende ist der Ausschnitt, den ich mir ausgedacht habe und der für mich funktioniert:

    static func topViewController() -> UIViewController? {
        return topViewController(vc: UIApplication.shared.keyWindow?.rootViewController)
    }

    private static func topViewController(vc:UIViewController?) -> UIViewController? {
        if let rootVC = vc {
            guard let presentedVC = rootVC.presentedViewController else {
                return rootVC
            }
            if let presentedNavVC = presentedVC as? UINavigationController {
                let lastVC = presentedNavVC.viewControllers.last
                return topViewController(vc: lastVC)
            }
            return topViewController(vc: presentedVC)
        }
        return nil
    }
Anil Arigela
quelle
0

Dies funktioniert hervorragend, um den Top ViewController 1 aus einer beliebigen Stammansicht zu finden

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

/* View Controller for Visible View */

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

Ich bin mir nicht sicher, ob dies helfen wird, was Sie erreichen möchten, indem Sie den obersten Ansichts-Controller finden, aber ich habe versucht, einen neuen Ansichts-Controller zu präsentieren. Wenn mein Root-Ansichts-Controller jedoch bereits einen modalen Dialog hatte, würde dieser blockiert, also ich würde mit diesem Code an die Spitze aller Modal View Controller fahren:

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

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

Sie können den obersten Ansichts-Controller mithilfe von finden

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Tapas Pal
quelle
Nur dass, wenn Sie die Frage tatsächlich lesen, selfkeine navigationControllerEigenschaft hat.
Hot Licks
0

Eine andere Lösung basiert auf der Responderkette, die je nach Ersthelfer möglicherweise funktioniert oder nicht:

  1. Holen Sie sich den Ersthelfer .
  2. Holen Sie sich den UIViewController, der diesem Ersthelfer zugeordnet ist .

Beispiel Pseudocode:

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

Schnell:

extension UIWindow {

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

class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if vc.isKindOfClass(UINavigationController.self) {

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

} else if vc.isKindOfClass(UITabBarController.self) {

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

} else {

    if let presentedViewController = vc.presentedViewController {

        return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)

    } else {

        return vc;
    }
}
}

Verwendung:

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