Holen Sie sich die meisten UIViewController

191

Ich kann nicht scheinen, die Spitze am meisten UIViewControllerohne Zugang zu einem zu bekommen UINavigationController. Folgendes habe ich bisher:

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

Es scheint jedoch nichts zu tun. Die keyWindowund rootViewControllerscheinen ebenfalls keine Werte zu sein, daher sollte die optionale Verkettung kein Problem darstellen.

HINWEIS: Es ist eine schlechte Idee, so etwas zu tun. Es bricht das MVC-Muster.

Zoyt
quelle
Hier ist eine alternative Lösung verfügbar stackoverflow.com/a/39994115/1872233
iDevAmit

Antworten:

283

presentViewControllerzeigt einen View Controller. Es wird kein View Controller zurückgegeben. Wenn Sie a nicht verwenden UINavigationController, suchen Sie wahrscheinlich nach presentedViewControllerund müssen am Stamm beginnen und die dargestellten Ansichten durchlaufen.

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

    // topController should now be your topmost view controller
}

Für Swift 3+:

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

    // topController should now be your topmost view controller
}

Für 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
}
Rickerbh
quelle
1
Kann jemand die while-Schleife erklären? Für mich sieht es so aus, als gäbe es nichts, worüber man sich drehen könnte. Ich bin mir nicht mal sicher, warum dies kompiliert wird.
Professor Tom
15
@ProfessorTom Die Schleife wird fortgesetzt, solange topController.presentedViewControlleretwas zurückgegeben wird (dh der Controller hat einen vorgestellten untergeordneten Controller). Es geht while letdarum, die Tatsache durchzusetzen, dass topController.presentedViewControlleretwas zurückgegeben werden muss. Wenn null zurückgegeben wird (dh der Controller hat keine untergeordneten untergeordneten Elemente), wird die Schleife beendet. Im Hauptteil der Schleife wird das untergeordnete Element als Strom neu zugewiesen topControllerund die Schleife erneut durchlaufen, wobei die Hierarchie des Ansichtscontrollers heruntergefahren wird. Es kann neu zugewiesen werden, topControllerwie es varin der äußeren ifAnweisung steht.
Rickerbh
1
Danke. Ich konnte online keine Beispiele dafür finden while let. Es gibt natürlich viele if letBeispiele.
Professor Tom
1
Die let x = somethingThatCouldBeNilSyntax ist ein sehr praktischer Trick, um überall dort zu verwenden, wo ein Wahrheitswert / eine Wahrheitsbedingung verwendet werden kann. Wenn wir es hier nicht verwenden würden, müssten wir explizit einen Wert zuweisen und dann testen, ob er tatsächlich vorhanden ist. Ich denke, es ist wirklich prägnant und ausdrucksstark.
Rickerbh
1
Ich bin mit dem Trick vertraut, es ist nur ein bisschen schwieriger, in while-Schleifen zu argumentieren - für die ich einen Mangel an Beispielen gefunden habe -, insbesondere in diesem.
Professor Tom
270

habe diese Erweiterung

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

Sie können dies überall auf Ihrem Controller verwenden

if let topController = UIApplication.topViewController() {

}
DLende
quelle
1
Vielen Dank für Ihren Erweiterungstipp :)
Thein
4
Ich habe versucht, eine wichtige Änderung an dieser Antwort vorzunehmen, diese wurde jedoch abgelehnt (ich habe keine Ahnung warum und die angegebenen Vorlagengründe waren nicht sinnvoll): Es ist wichtig zu überprüfen, ob der nav.visibleViewController Null ist, bevor Sie ihn in der Rekursion verwenden Aufruf (genau wie tab.selectedViewController aktiviert ist), da Sie sonst, wenn es Null wäre, in eine rekursive Endlosschleife geraten würden.
Ethan G
@EthanG Wenn nach meinem Verständnis nav.visibleViewController nil ist, gibt die Funktion nil zurück (bis zum letzten fallen lassen return). Wie kann es in eine Endlosschleife kommen?
Desmond DAI
3
Ich denke, es wäre logischer, dies als statische Funktion von UIViewController
Leszek Zarna
1
Die Prüfung 'präsentierter ViewController' sollte wahrscheinlich an erster Stelle stehen, wenn Sie modal präsentierte View Controller auf UITabBarControllern
abfangen
65

Für schnelles 4/5 +, um den obersten ViewController zu erhalten

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

Wie benutzt man

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}
Hardik Thakkar
quelle
2
Geniale Lösung. Vielen Dank!
Andrey M.
2
'keyWindow' war in iOS 13.0 veraltet.
Rs7
2
'keyWindow' war in 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
            }
        }
    }
}

Verwendung:

if let topController = window.visibleViewController() {
    println(topController)
}
Bobj-C
quelle
Diese Lösung sah wirklich vielversprechend aus, aber ich habe versucht, dies auszuführen, um den View-Controller zu erhalten, auf dem ich mich befinde, wenn ich eine Push-Benachrichtigung erhalte, und es gab einen Null-Fehler auf demreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike
@Mike Sie müssen nur den präsentierten ViewController verwenden, nicht den präsentierten ViewController. präsentiertViewController
Allaire
@allaire Wenn Sie einen Modal View Controller über einem Modal View Controller präsentiert haben, brauchen Sie .presentedViewController.presentedViewControlleroder nicht?
Baran Emre
6

Basierend auf der Antwort von Dianz, der Objective-C-Version

- (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;
}
Tibidabo
quelle
Funktioniert nicht für UINavigationController in UITabBarController. es wird UINavigationController zurückgeben, sollte der topController in der Navigation stecken bleiben.
Mike.R
Tnx Tnx Tnx Bro
reza_khalafi
6

ich liebte die Antwort von @ dianz , und hier ist die Version von Swift 3. Es ist im Grunde das Gleiche, aber ihm fehlte eine geschweifte Klammer und einige der Syntax- / Variablen- / Methodennamen haben sich geändert. Hier ist es also!

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

Die Verwendung ist jedoch immer noch genau die gleiche:

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

https://gist.github.com/db0company/369bfa43cb84b145dfd8 Ich habe einige Tests zu den Antworten und Kommentaren auf dieser Site durchgeführt. Für mich funktioniert folgendes

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

Holen Sie sich dann den Top ViewController von:

UIApplication.shared.topMostViewController()
Albert Zou
quelle
5

Verwenden Sie diesen Code, um den besten UIViewController zu finden

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}
Bibin Joseph
quelle
2
Wie unterscheidet sich das von Rickerbhs Antwort?
ElectroBuddha
5

Leichte Variation von @AlberZou unter Verwendung einer berechneten Variablen anstelle einer Funktion

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

Dann sage

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}
Ryan Heitner
quelle
4

Basierend auf Bob-c oben:

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;
            }
        }
    }
}
Daniel
quelle
3

Zu viele Geschmacksrichtungen, aber keine iterativ ausgearbeitete. Kombiniert von den vorherigen:

     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
    }
Jaime Agudo
quelle
2

Sie können eine UIViewController-Variable in AppDelegate definieren und in jeder viewWillAppear die Variable auf self setzen (jedoch ist die Dianz-Antwort die beste Antwort.)

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}
Arash Jamshidi
quelle
1
Vielen Dank, es funktioniert gut für mich als die andere Lösung, wenn es versucht, den Navigationscontroller zu bekommen, gibt es null zurück, so dass ich keinen neuen VC pushen konnte
Amr Angry
Stellen Sie sicher, dass die aktuelle VC als schwache Referenz definiert ist, da sonst ein Speicherverlust auftritt.
Bubuxu
2

So finden Sie den sichtbaren viewController in Swift 3

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

     let prefs = UserDefaults.standard

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

     }
}

Dieser Code findet den zuletzt hinzugefügten oder den zuletzt aktiven Controller sichtbar.

Dies habe ich in AppDelegate verwendet, um Active View Controller zu finden

Prateekro
quelle
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
    }
}
Rakesh Kusuma
quelle
Mehrdeutige Verwendung von 'visibleViewController'
Omar N Shamali
1

Wo haben Sie den Code eingegeben?

Ich versuche Ihren Code in meiner Demo, ich habe herausgefunden, ob Sie den Code eingeben

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

wird fehlschlagen, da das Schlüsselfenster noch eingestellt wurde.

Aber ich habe Ihren Code in einige View-Controller eingefügt

override func viewDidLoad() {

Es funktioniert einfach.

Tinyfool
quelle
Es ist nicht in didFinishLaunchingWithOptions. Ich brauche dies nur für verschiedene Debug-Zwecke.
Zoyt
1

In einem sehr seltenen Fall mit benutzerdefiniertem Übergang befindet sich der oberste Ansichts-Controller nicht in einem Navigationsstapel oder einem Registerkarten-Controller oder wird angezeigt, sondern seine Ansicht wird oben in den Unteransichten des Schlüsselfensters eingefügt.

In einer solchen Situation muss überprüft werden UIApplication.shared.keyWindow.subviews.last == self.view, ob der aktuelle Ansichts-Controller der oberste ist.

BabyPanda
quelle
1

Für alle, die eine schnelle 5 / iOS 13+ Lösung suchen ( keywindowist seit iOS 13 veraltet)

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
        }
    }
}
Virendra
quelle
0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }
Den
quelle
0

Die beste Lösung für mich ist eine Erweiterung mit einer Funktion. Erstellen Sie eine schnelle Datei mit dieser Erweiterung

Erstens ist die UIWindow-Erweiterung :

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

innerhalb dieser Datei Add-Funktion

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

Und wenn Sie es verwenden möchten, können Sie es überall aufrufen. Beispiel :

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

Der Dateicode lautet wie folgt :

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