Obtenga la mayoría de UIViewController

192

Parece que no puedo obtener el máximo UIViewControllersin acceso a a UINavigationController. Esto es lo que tengo hasta ahora:

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

Sin embargo, no parece hacer nada. Los keyWindowy rootViewControllerparecen ser valores no nulos también, por lo que el encadenamiento opcional no debería ser un problema.

NOTA: es una mala idea hacer algo como esto. Rompe el patrón MVC.

Zoyt
fuente
Aquí hay una solución alternativa disponible stackoverflow.com/a/39994115/1872233
iDevAmit el

Respuestas:

287

presentViewControllermuestra un controlador de vista. No devuelve un controlador de vista. Si no está utilizando un UINavigationController, probablemente esté buscando presentedViewControllery deberá comenzar desde la raíz e iterar hacia abajo a través de las vistas presentadas.

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

    // topController should now be your topmost view controller
}

Para Swift 3+:

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

    // topController should now be your topmost view controller
}

Para 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
fuente
1
¿Alguien puede explicar el ciclo while? Para mí, parece que no hay nada para recorrer; Ni siquiera estoy seguro de por qué esto se compila.
Profesor Tom
15
@ProfessorTom El ciclo continúa mientras se topController.presentedViewControllerdevuelve algo (es decir, el controlador tiene un controlador secundario presentado). Es while letpara hacer cumplir el hecho de que topController.presentedViewControllerdebe devolver algo. Si devuelve nil (es decir, este controlador no tiene hijos presentados), entonces dejará de repetirse. En el cuerpo del bucle, reasigna al hijo como el actual topControllery vuelve a recorrerlo, bajando la jerarquía del controlador de vista. Se puede reasignar topControllercomo es un varen la ifdeclaración externa .
rickerbh
1
gracias. No he podido encontrar ningún ejemplo en línea de while let. Por supuesto, hay muchos if letejemplos por encontrar.
Profesor Tom
1
La let x = somethingThatCouldBeNilsintaxis es un truco muy útil para usar en cualquier lugar donde se pueda usar un valor / condición de verdad. Si no lo usáramos aquí, tendríamos que asignar explícitamente un valor, luego probar para ver si realmente está allí. Creo que es realmente sucinto y expresivo.
rickerbh
1
Estoy familiarizado con el truco, es un poco más difícil razonar sobre los bucles while, para lo cual he encontrado una escasez de ejemplos, especialmente este.
Profesor Tom
272

tener esta extensión

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

Puedes usar esto en cualquier lugar de tu controlador

if let topController = UIApplication.topViewController() {

}
DLende
fuente
1
Gracias por su consejo de extensión :)
Thein
44
Intenté hacer una edición importante para esta respuesta, pero fue rechazada (no tengo idea de por qué y las razones de la plantilla dadas no tenían sentido): es importante verificar si nav.visibleViewController es nulo antes de usarlo en el recursivo llamar (al igual que cómo se marca tab.selectedViewController) porque de lo contrario, si fuera nulo, entraría en un bucle infinito recursivo.
Ethan G
@EthanG Según tengo entendido, si nav.visibleViewController es nil, la función devolverá nil (caerá al último return). ¿Cómo puede entrar en un bucle infinito?
Desmond DAI
3
Creo que sería más lógico hacer esto como una función estática de UIViewController
Leszek Zarna
1
El cheque 'presentedViewController' probablemente debería ser lo primero, si se desea capturar los controladores de vista modal presentados en UITabBarControllers ..
Tokuriku
65

Para un rápido 4/5 + para obtener la vista más alta

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

Cómo utilizar

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}
Hardik Thakkar
fuente
2
Solución brillante ¡Gracias!
Andrey M.
2
'keyWindow' quedó en desuso en iOS 13.0.
rs7
2
'keyWindow' quedó en desuso en iOS 13.0 stackoverflow.com/a/57899013/4514671
Rebeloper
19
extension UIWindow {

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

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

Uso:

if let topController = window.visibleViewController() {
    println(topController)
}
Bobj-C
fuente
esta solución parecía realmente prometedora, sin embargo, intenté ejecutar esto para obtener el controlador de vista en el que estoy cuando recibí una notificación de inserción y arrojó un error nulo en elreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike
@Mike solo necesita usar presentadoViewController, no presentadoViewController. presentadoViewController
allaire
@allaire Si presentó un controlador de vista modal encima de un controlador de vista modal, ¿lo necesita .presentedViewController.presentedViewControllero no?
Baran Emre
6

Basado en la respuesta de Dianz, la versión Objective-C

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

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

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}
Tibidabo
fuente
No funcionará para UINavigationController en UITabBarController. devolverá UINavigationController, debería devolver topController en la navegación atascada.
Mike.R
Tnx Tnx Tnx Bro
reza_khalafi
6

Me encantó la respuesta de @ dianz , así que aquí está la versión de Swift 3. Básicamente es lo mismo, pero le faltaba una llave y algunos de los nombres de sintaxis / variable / método han cambiado. ¡Asi que aqui esta!

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

Sin embargo, el uso sigue siendo exactamente el mismo:

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

https://gist.github.com/db0company/369bfa43cb84b145dfd8 Hice algunas pruebas sobre las respuestas y comentarios en este sitio. Para mí, los siguientes trabajos

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

Luego, obtenga la vista superior Controlador por:

UIApplication.shared.topMostViewController()
Albert Zou
fuente
5

Use este código para encontrar los mejores UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}
Bibin Joseph
fuente
2
¿Cómo es esto diferente de la respuesta de rickerbh?
ElectroBuddha
5

Variación leve en @AlberZou usando una variable calculada en lugar de una función

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

Luego dice

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

Basado en Bob -c arriba:

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
fuente
4

Demasiados sabores pero ninguno uno elaborado e iterativo. Combinado de los anteriores:

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

puede definir una variable UIViewController en AppDelegate, y en cada vista WillAppear establecer la variable en self (sin embargo, dianz answer es la mejor respuesta).

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}
Arash Jamshidi
fuente
1
muchas gracias que funcione bien para mí como la otra solución cuando se trata de conseguir el navigationControll que vuelva a cero, así que era't capaz de empujar cualquier nuevo vc
Amr enojado
Asegúrese de que el currentVC definido como referencia débil, o tendrá pérdida de memoria.
bubuxu
2

Para encontrar la vista visible Controlador en Swift 3

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

     let prefs = UserDefaults.standard

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

     }
}

Este código encuentra visible el último controlador agregado o el último controlador activo.

Esto lo he usado en AppDelegate para encontrar el controlador de vista activa

Prateekro
fuente
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
fuente
Uso ambiguo de 'visibleViewController'
Omar N Shamali
1

¿Dónde pusiste el código?

Probé tu código en mi demo, descubrí, si pones el código en

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

fallará, porque la ventana clave se ha configurado todavía.

Pero puse tu código en algún controlador de vista

override func viewDidLoad() {

Simplemente funciona

Tinyfool
fuente
No está en didFinishLaunchingWithOptions. Solo necesito esto para varios propósitos de depuración.
Zoyt
1

En un caso muy raro, con segue personalizado, el controlador de la vista superior no está en una pila de navegación o controlador de barra de pestañas o presentado, pero su vista se inserta en la parte superior de las subvistas de ventanas clave.

En tal situación, es necesario verificar si UIApplication.shared.keyWindow.subviews.last == self.viewpara determinar si el controlador de vista actual es el más alto.

BabyPanda
fuente
1

Para cualquiera que busque una solución rápida 5 / iOS 13+ ( keywindowestá en desuso desde iOS 13)

extension UIApplication {

    class func getTopMostViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        } else {
            return nil
        }
    }
}
Virendra
fuente
¿Cómo lo usaría?
Chris Comas
Solo llámalo así. UIApplication.getTopMostViewController()dentro de su ViewController. @ChrisComas
Virendra
0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }
Guarida
fuente
0

La mejor solución para mí es una extensión con una función. Crea un archivo rápido con esta extensión

Primero es la extensión UIWindow :

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

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

dentro de esa función de agregar archivo

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

Y si desea usarlo, puede llamarlo en cualquier lugar. Ejemplo :

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

El código del archivo es así :

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
fuente