Cómo encontrar el controlador de vista superior en iOS

253

Me he encontrado con un par de casos en los que sería conveniente poder encontrar el controlador de vista "superior" (el responsable de la vista actual), pero no he encontrado la manera de hacerlo.

Básicamente, el desafío es este: dado que uno está ejecutando en una clase que no es un controlador de vista (o una vista) [y no tiene la dirección de una vista activa] y no se le ha pasado la dirección del controlador de vista superior ( o, digamos, la dirección del controlador de navegación), ¿es posible encontrar ese controlador de vista? (Y, si es así, ¿cómo?)

O, en su defecto, ¿es posible encontrar la vista más alta?

Hot Licks
fuente
Entonces estás diciendo que no es posible.
Hot Licks
@Daniel no, estoy diciendo que parece que tu código podría usar algunos rediseños, porque rara vez deberías saber esto. Además, la idea de "superior" solo es válida en ciertos contextos, e incluso entonces no siempre.
Dave DeLong
@Daniel Había leído mal tu pregunta. Hay muchos peros y peros tratando de responder a este. Depende del flujo de su controlador de vista. La respuesta de @ Wilbur debería ser un buen punto de partida para rastrearla.
Deepak Danduprolu
Bueno, simplifiquemoslo a un caso específico. Si quisiera escribir un clon de UIAlertView, ¿cómo lo haría? Tenga en cuenta que puede funcionar bien sin pasar ninguna direccionamiento a otros controladores o vistas.
Hot Licks
44
@Daniel: Agregar una segunda UIWindow funciona bien para superposiciones similares a las vistas de alertas.
Wilbur Vandrsmith

Respuestas:

75

iOS 4 introdujo la propiedad rootViewController en UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

Sin embargo, deberá configurarlo usted mismo después de crear el controlador de vista.

Wilbur Vandrsmith
fuente
155
Wilbur, esto te dará lo contrario de lo que pidió el operador. rootViewController es el controlador de vista base en lugar de la parte superior.
m4rkk
3
m4rkk: "Top-most" depende de la dirección desde la que esté mirando. ¿Se agregan nuevos controladores a la parte superior (tipo pila) o al fondo (tipo árbol)? En cualquier caso, el OP mencionó que el controlador de navegación estaba en la parte superior, lo que implica la vista de crecimiento hacia abajo.
Wilbur Vandrsmith
50
La palabra "top" se usa para el controlador de vista, es decir, visualmente en la parte superior (como -[UINavigationController topViewController]). Luego está la palabra "raíz", que es la raíz del árbol (como -[UIWindow rootViewController].
Tricertops
13
@ImpurestClub No puedo encontrarlo en la documentación , tampoco parece que Xcode lo encuentre.
Drux
44
@adib no, pertenece a UINavigationController
David H
428

Creo que necesita una combinación de la respuesta aceptada y @ 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
fuente
44
Además, puede verificarlo UINavigationControllery solicitarlo topViewControllero incluso verificarlo UITabBarControllery solicitarlo selectedViewController. Esto le dará el controlador de vista que actualmente es visible para el usuario.
Tricertops 01 de
33
Esta es una solución incompleta, ya que solo atraviesa la jerarquía de los controladores de vista presentados de forma modal, no la jerarquía de childViewControllers (como los usa UINavigationController, UITabBarController, etc.).
algal
3
Esta es una excelente manera de resumir la presentación de un controlador de vista modal que se reanuda al estado actual de la aplicación, en mi caso fue una pantalla de reingreso de contraseña después de que la aplicación agotó el tiempo de espera. ¡Gracias!
erversteeg
11
@algal: no realmente: UITabBarController, UINavigationController ya son los principales controladores de vista en la jerarquía. Dependiendo de lo que desee hacer con el "controlador superior", es posible que no desee atravesarlos y jugar con su contenido. En mi caso, fue presentar un controlador modal por encima de todo, ¡y para eso necesito obtener el UINaviationController o UITabBarController, no su contenido !
Rick77
1
@ Rick77, si esto es cierto, tu pequeño comentario enterrado aquí hace innecesarias las toneladas de modificaciones complicadas en las otras respuestas. Como nadie más menciona esto en absoluto, siento que tengo que pedirte que afirmes que es verdad. Y si es así, es tan importante que merece ser una respuesta propia. Porque la gran mayoría de las otras respuestas hacen volteretas al tratar de abordar este problema. ¡Estarías salvando vidas!
Le Mot Juiced
150

Para completar la respuesta de JonasG (que omitió los controladores de la barra de pestañas mientras recorría), aquí está mi versión de devolver el controlador de vista actualmente visible:

- (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
fuente
2
Bien, sí, me olvidé de los controladores TabBar: P
JonasG
99
No incluyechildViewControllers
Awesome-o
Mire mi respuesta a continuación, que mejora la respuesta anterior al manejar los casos que @kleo dejó fuera, como popovers, los controladores de vista agregados como subvistas a algunos otros controladores de vista mientras recorre
Rajesh
Si está utilizando return [self topViewControllerWithRootViewController: navigationController.visibleViewController] ;, visibleViewController devuelve el controlador de vista presentado (SI ES CUALQUIER), incluso si es un UIAlertController. Para alguien que necesita evitar el controlador de alerta de interfaz de usuario, use topViewController en lugar de visibleViewController
Johnykutty
Solo para agregar mi 50 centavo a esto: estaba luchando para que esto funcionara en mi controlador de vista que carga un webView ... la razón por la que no pude hacer que esto funcionara fue porque la vista aún no estaba lista (no terminó de cargar) y por lo tanto no era visible. Eso condujo a una situación en la que fallaba la obtención de un TopViewContoller, porque UINavigationController estaba tratando de obtener un ViewController visible mientras todavía no había ViewController visible. Entonces, si alguien se enfrenta a este problema, asegúrese de que su vista termine de cargarse antes de hacer una llamada al método topViewController anterior.
mbuster
52

Una versión completa no recursiva, que se ocupa de diferentes escenarios:

  • El controlador de vista presenta otra vista
  • El controlador de vista es un UINavigationController
  • El controlador de vista es un UITabBarController

C objetivo

 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
fuente
2
Lo nombré visibleViewControllerpara dejar en claro lo que hace.
Jonny
31

Obteniendo el mejor controlador de vista para Swift usando extensiones

Código:

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

Uso:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()
Varuna
fuente
excelente, muchas gracias por esta solución. ¡Se necesitaba el truco de las subvistas! Nuevamente, muchas gracias, me salvaste el día.
iKK
25

Para completar la respuesta de Eric (que omitió popovers, controladores de navegación, controladores de barra de pestañas, controladores de vista agregados como subvistas a otros controladores de vista durante el desplazamiento), aquí está mi versión de devolver el controlador de vista actualmente visible:

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

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

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

Y ahora todo lo que necesita hacer para obtener el mejor controlador de vista es llamar al método anterior de la siguiente manera:

UIViewController *topMostViewControllerObj = [self topViewController];
Rajesh
fuente
¿Falta SplitViewController también?
apinho
21

Esta respuesta incluye childViewControllersy mantiene una implementación limpia y legible.

+ (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;
}
Awesome-o
fuente
Se actualizó algún código, también se debe mostrar qué controlador es minimizando y restaurando nuevamente. nik-kov-ios-developer.blogspot.ru/2016/12/…
Nik Kov
Oye, vamos, ¿dónde está tu "topVisibleViewController"?
Paradise
12

Hace poco recibí esta situación en uno de mis proyectos, que requería mostrar una vista de notificación, independientemente del controlador que se mostrara y del tipo (UINavigationController, controlador clásico o controlador de vista personalizado), cuando el estado de la red cambió.

Así que simplemente publiqué mi código, que es bastante fácil y en realidad se basa en un protocolo para que sea flexible con cada tipo de controlador de contenedor. Parece estar relacionado con las últimas respuestas, pero de una manera muy flexible.

Puedes obtener el código aquí: PPTopMostController

Y obtuve el mejor controlador usando

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

Esta es una mejora a la respuesta de 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) Es una función auxiliar.

¡Ahora todo lo que necesita hacer es llamar topMostController()y se debe devolver la mayor cantidad de UIViewController!

JonasG
fuente
77
Desde 1983 yo diría. Recuerde que Objective-C contiene C ... Envolver el código ObjC en funciones C es una práctica común, así que sí, este es el código Objective-C.
JonasG
@JonasG Hola Jonas, ¿en qué circunstancias prefiere envolver el código ObjC en C? Porque, a veces veo funciones de C como esta y no puedo distinguir el uso. ¿El código de ajuste en C proporciona algún beneficio de rendimiento?
OzBoz
1
@OzBoz En situaciones en las que no está claro de inmediato a qué clase selfdebería pertenecer.
adib
8

Aquí está mi opinión sobre esto. Gracias a @Stakenborg por señalar la forma de omitir obtener UIAlertView como el controlador más superior

-(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
fuente
Debe evitar los métodos de nomenclatura como getSomething:en Objective-C. Esto tiene un significado especial (más: cocoadevcentral.com/articles/000082.php ) y no cumple con estos requisitos en su código.
Vive el
7
@implementation UIWindow (Extensiones)

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

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

    volver topController;
}

@final
FishStix
fuente
No creo que hayas cumplido la condición establecida en la publicación original.
Hot Licks el
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
fuente
Usé esto, pero tenga en cuenta que se rompe cuando hay más de un controlador de vista presentado
Chuck Boris,
7

Para la última versión de Swift:
cree un archivo, asígnele un nombre UIWindowExtension.swifty pegue el siguiente fragmento:

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
}

Úselo en cualquier lugar como:

if let topVC = getTopViewController() {

}
Ashok
fuente
No quiero cambiar demasiado su respuesta, pero sugeriría algunas cosas. 1. Agregue soporte para UISplitViewController. 2. utilizar en switchlugar de si más. 3. No estoy seguro de que necesite una función estática también, creo que podría hacerlo fácilmente en el nivel de primera instancia var que declaró. 4. Probablemente sea mejor no crear demasiadas funciones globales, pero eso es cuestión de gustos. Puede usar una línea de código para lograr el efecto de la función global:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Jordan Smith
7

Extensión simple para UIApplicationen Swift:

NOTA:

Se preocupa por 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
    }
}

Uso simple:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}
Bartłomiej Semańczyk
fuente
SÍ SÍ SÍ: hay muchas soluciones en la web para encontrar el TopMostViewController, pero si su aplicación tiene una barra de pestañas con una pestaña Más, DEBE manejarla un poco diferente.
Andy Obusek
7

Use la extensión a continuación para tomar la corriente visible UIViewController. Trabajó para Swift 4.0 y posterior

Swift 4.0 y posterior:

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

¿Cómo utilizar?

let objViewcontroller = UIApplication.topViewController()
COVID-19
fuente
¿No debería esta prueba presentedViewControllerprimero, antes de los casos UINavigationControllery UITabBarController? De lo contrario, si un controlador de vista se presenta modalmente desde un UINavigationControllero UITabBarController, no se devolverá como el controlador de vista superior, aunque sea el controlador de vista el que esté visible.
Dibujó
4

Sin embargo, otra solución rápida

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

Extensión 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
    }
}

Úselo desde cualquier lugar como,

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

o como

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

Se ajusta a cualquier clase como UINavigationController, UITabBarController

¡Disfrutar!

Saranjith
fuente
1
@BilalBakhrom dice "Upvoted. Creo que su respuesta es la mejor. No puede llamar directamente al método topViewController (). La clase UIApplication es singleton, use una instancia llamada" shared "." en una edición que he votado rechazar. Si esto es realmente correcto, haga clic aquí para acceder a un menú donde puede aprobar esa edición.
wizzwizz4
3

Esto es lo que funcionó para mí.

Descubrí que a veces el controlador era nulo en la ventana clave, ya que keyWindow es algo del sistema operativo como una alerta, etc.

 + (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
fuente
3

Ampliando la respuesta de @ Eric, debe tener cuidado de que keyWindow sea en realidad la ventana que desea. Si está intentando utilizar este método después de tocar algo en una vista de alerta, por ejemplo, keyWindow será en realidad la ventana de alerta, y eso sin duda le causará problemas. Esto me sucedió en la naturaleza cuando manejaba enlaces profundos a través de una alerta y causaba SIGABRT sin RASTREO DE APILAMIENTO. Perra total para depurar.

Aquí está el código que estoy usando ahora:

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

Siéntase libre de mezclar esto con cualquier sabor de recuperar el controlador de vista superior que desee de las otras respuestas a esta pregunta.

Stakenborg
fuente
¿Has encontrado que esta es una solución completa? Muchas de las otras respuestas son enormemente complicadas e intentan explicar tantos casos extremos. Yo quiero que esto es cierto, es tan simple y elegante.
Le Mot Juiced
Nunca he tenido un problema con eso. Si no está haciendo nada inusual con su pila de navegación, esto debería funcionar, de lo contrario, algunas de las otras soluciones manejan casos más complicados.
Stakenborg
3

Solución alternativa rápida:

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

    return topController!
}
Esqarrouth
fuente
3

Esta solución es la más completa. Toma en consideración: UINavigationController UIPageViewController UITabBarController Y el controlador de vista más arriba presentado desde el controlador de vista superior

El ejemplo está en Swift 3.

Hay 3 sobrecargas

//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
fuente
3

Una solución concisa pero completa en Swift 4.2, tiene en cuenta los controles UINavigationControllers , UITabBarControllers , presentados y los controladores de vista secundarios :

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

Uso:

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

Gran solución en Swift, implementar en 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
}
Edward Novelo
fuente
1

Creo que la mayoría de las respuestas se han ignorado por completo UINavigationViewController, por lo que manejé este caso de uso con la siguiente implementación.

+ (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
fuente
1

Sé que es muy tarde y podría ser redundante. Pero el siguiente es el fragmento que se me ocurrió y que funciona para mí:

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

Esto funciona muy bien para encontrar el controlador de vista superior 1 desde cualquier control de vista raíz

+ (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
fuente
0

No estoy seguro de si esto ayudará a lo que está tratando de lograr al encontrar el controlador de vista superior, pero estaba tratando de presentar un nuevo controlador de vista, pero si mi controlador de vista raíz ya tenía un diálogo modal, estaría bloqueado, por lo que iría a la parte superior de todos los controladores de vista modal usando este código:

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

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

puedes encontrar el controlador de vista más alto usando

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Tapas Pal
fuente
Excepto que, si realmente lees la pregunta, selfno tiene ninguna navigationControllerpropiedad.
Hot Licks
0

Otra solución se basa en la cadena de respuesta, que puede o no funcionar dependiendo de cuál sea la primera respuesta:

  1. Consigue el primer respondedor .
  2. Obtenga el UIViewController asociado con ese primer respondedor .

Pseudocódigo de ejemplo:

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

Rápido:

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

Uso:

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