Obtenga el UIViewController que se muestra actual en la pantalla en AppDelegate.m

126

La corriente UIViewControlleren la pantalla debe responder a las notificaciones automáticas de los APN, configurando algunas vistas de insignia. Pero, ¿cómo podría obtener el UIViewControllermétodo in application:didReceiveRemoteNotification: de AppDelegate.m?

Intenté usar self.window.rootViewControllerpara obtener la visualización actual UIViewController, puede ser un UINavigationViewControlleru otro tipo de controlador de vista. Y descubro que la visibleViewControllerpropiedad de UINavigationViewControllerse puede utilizar para obtener el UIViewControlleren la pantalla. Pero, ¿qué podría hacer si no es un UINavigationViewController?

Cualquier ayuda es apreciada! El código relacionado es el siguiente.

AppDelegate.m

...
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

    //I would like to find out which view controller is on the screen here.

    UIViewController *vc = [(UINavigationViewController *)self.window.rootViewController visibleViewController];
    [vc performSelector:@selector(handleThePushNotification:) withObject:userInfo];
}
...

ViewControllerA.m

- (void)handleThePushNotification:(NSDictionary *)userInfo{

    //set some badge view here

}
lu yuan
fuente

Respuestas:

99

También puede usar el rootViewControllercuando su controlador no es un UINavigationController:

UIViewController *vc = self.window.rootViewController;

Una vez que conoce el controlador de vista raíz, depende de cómo haya construido su IU, pero posiblemente pueda encontrar una manera de navegar a través de la jerarquía de controladores.

Si proporciona más detalles sobre la forma en que definió su aplicación, entonces podría darle más pistas.

EDITAR:

Si desea la vista superior (no el controlador de vista), puede verificar

[[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];

aunque esta vista puede ser invisible o incluso estar cubierta por algunas de sus subvistas ...

De nuevo, depende de su interfaz de usuario, pero esto podría ayudar ...

sergio
fuente
19
El problema con esto es si la vista visible no pertenece al controlador de vista raíz (en el caso de vistas modales y tal).
Dima
Sí. Pero tal vez sea un UITabViewController. ¿No hay un método directo para obtener el UIViewController en la pantalla?
lu yuan
2
bueno, ya ves, UINavigationController te proporciona una forma de saber qué controlador es el más importante; su controlador raíz debe proporcionar la misma información de alguna manera. No se puede inferir en general porque depende estrictamente de cómo construyó su IU y no existe una jerarquía explícita de controladores (como sucede con las vistas). Simplemente puede agregar una propiedad a su controlador raíz y establecer su valor cada vez que "presione" un nuevo controlador en la parte superior.
sergio
1
Siempre y cuando el valor se mantenga actualizado, esa también parece ser una buena forma de hacerlo.
Dima
44
No hay una forma directa de llegar al controlador desde una UIViewinstancia. norootViewController es necesariamente el controlador que se muestra actualmente. Está justo en la parte superior de la jerarquía de vistas.
Gingi
101

Siempre me encantan las soluciones que involucran categorías, ya que se atornillan y se pueden reutilizar fácilmente.

Entonces creé una categoría en UIWindow. Ahora puede llamar a visibleViewController en UIWindow y esto le dará el controlador de vista visible buscando en la jerarquía del controlador. Esto funciona si está utilizando la navegación y / o el controlador de barra de pestañas. Si tiene otro tipo de controlador para sugerir, hágamelo saber y puedo agregarlo.

UIWindow + PazLabs.h (archivo de encabezado)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (archivo de implementación)

#import "UIWindow+PazLabs.h"

@implementation UIWindow (PazLabs)

- (UIViewController *)visibleViewController {
    UIViewController *rootViewController = self.rootViewController;
    return [UIWindow getVisibleViewControllerFrom:rootViewController];
}

+ (UIViewController *) getVisibleViewControllerFrom:(UIViewController *) vc {
    if ([vc isKindOfClass:[UINavigationController class]]) {
        return [UIWindow getVisibleViewControllerFrom:[((UINavigationController *) vc) visibleViewController]];
    } else if ([vc isKindOfClass:[UITabBarController class]]) {
        return [UIWindow getVisibleViewControllerFrom:[((UITabBarController *) vc) selectedViewController]];
    } else {
        if (vc.presentedViewController) {
            return [UIWindow getVisibleViewControllerFrom:vc.presentedViewController];
        } else {
            return vc;
        }
    }
}

@end

Versión rápida

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
            }
        }
    }
}
zirinisp
fuente
2
¿Cómo puedo usar esto para la versión rápida?
Vijay Singh Rana
2
No puedo entender tu pregunta. Copia y pega dentro de tu código.
zirinisp
¿Qué pasa con el contenedor personalizado VC?
Mingming
@Mingming no debería ser tan difícil agregar un extra si se verifica si es el contenedor personalizado VC (en el método getVisibielController) y si es así, devuelve el controlador "visible", que normalmente sería vc.childControllers.lastObject para la mayoría de los personalizados implementaciones de contenedor VC (supongo), pero dependería de cómo se implementa
gadu
1
Acabo de publicar una respuesta con el mismo enfoque que en esta respuesta, excepto por una sintaxis actualizada: está usando un caso de cambio y sigue las convenciones de nomenclatura de Swift 3: stackoverflow.com/a/42486823/3451975
Jeehut
43

Extensión simple para la aplicación UIA en Swift (se preocupa aún más por másNavigationController dentro UITabBarControllerdel iPhone) :

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController where top.view.window != nil {
                return topViewController(top)
            } else if let selected = tab.selectedViewController {
                return topViewController(selected)
            }
        }

        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }

        return base
    }
}

Uso simple:

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

Funciona perfecto :-)

ACTUALIZACIÓN para código limpio:

extension UIViewController {
    var top: UIViewController? {
        if let controller = self as? UINavigationController {
            return controller.topViewController?.top
        }
        if let controller = self as? UISplitViewController {
            return controller.viewControllers.last?.top
        }
        if let controller = self as? UITabBarController {
            return controller.selectedViewController?.top
        }
        if let controller = presentedViewController {
            return controller.top
        }
        return self
    }
}
Bartłomiej Semańczyk
fuente
1
Esto parece ser el código para Swift 2.x. Swift 3.x ya no tiene "dónde". Además, "sharedApplication ()" ahora está "compartido". No es un gran trato. Solo lleva un minuto actualizar. Podría ser bueno mencionar que usa la recursividad. Además, cada llamada a topViewController debe necesitar el prefijo "base:".
Jeff Muir
37

También puede publicar una notificación a través de NSNotificationCenter. Esto le permite lidiar con una serie de situaciones en las que atravesar la jerarquía del controlador de vista puede ser complicado, por ejemplo, cuando se presentan modales, etc.

P.ej,

// MyAppDelegate.h
NSString * const UIApplicationDidReceiveRemoteNotification;

// MyAppDelegate.m
NSString * const UIApplicationDidReceiveRemoteNotification = @"UIApplicationDidReceiveRemoteNotification";

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

    [[NSNotificationCenter defaultCenter]
     postNotificationName:UIApplicationDidReceiveRemoteNotification
     object:self
     userInfo:userInfo];
}

En cada uno de sus controladores de vista:

-(void)viewDidLoad {
    [[NSNotificationCenter defaultCenter] 
      addObserver:self
      selector:@selector(didReceiveRemoteNotification:)                                                  
      name:UIApplicationDidReceiveRemoteNotification
      object:nil];
}

-(void)viewDidUnload {
    [[NSNotificationCenter defaultCenter] 
      removeObserver:self
      name:UIApplicationDidReceiveRemoteNotification
      object:nil];
}

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo {
    // see http://stackoverflow.com/a/2777460/305149
   if (self.isViewLoaded && self.view.window) {
      // handle the notification
   }
}

También podría usar este enfoque para los controles de instrumentos que necesitan actualizarse cuando se recibe una notificación y son utilizados por varios controladores de vista. En ese caso, maneje las llamadas de observador de agregar / quitar en los métodos init y dealloc, respectivamente.

Aneil Mallavarapu
fuente
1
¿Qué hay addObserver:baradentro viewDidLoad? ¿Tengo que reemplazar con self?
CainaSouza
Gracias por señalarlo, debería ser uno mismo. Actualizaré la respuesta.
Aneil Mallavarapu
se bloquea al obtener todas las claves de userInfo .. ¿Alguna idea? [NSConcreteNotification allKeys]: selector no reconocido enviado a la instancia 0x1fd87480 2013-07-05 16: 10: 36.469 Providence [2961: 907] *** Finalización de la aplicación debido a la excepción no detectada 'NSInvalidArgumentException', razón: '- [NSConcreteNotification allKeys]: no reconocido selector enviado a la instancia 0x1fd87480 '
Awais Tariq
@AwaisTariq - Hmmm - supongo que el objeto pasado por iOS a didReceiveRemoteNotification no es en realidad un NSDictionary, como lo especifica la interfaz.
Aneil Mallavarapu
¿Qué sucede si el usuario aún no ha navegado a su clase de observador? : /
halbano
15

Código

Aquí hay un enfoque que utiliza la gran sintaxis de mayúsculas y minúsculas en Swift 3/4/5 :

extension UIWindow {
    /// Returns the currently visible view controller if any reachable within the window.
    public var visibleViewController: UIViewController? {
        return UIWindow.visibleViewController(from: rootViewController)
    }

    /// Recursively follows navigation controllers, tab bar controllers and modal presented view controllers starting
    /// from the given view controller to find the currently visible view controller.
    ///
    /// - Parameters:
    ///   - viewController: The view controller to start the recursive search from.
    /// - Returns: The view controller that is most probably visible on screen right now.
    public static func visibleViewController(from viewController: UIViewController?) -> UIViewController? {
        switch viewController {
        case let navigationController as UINavigationController:
            return UIWindow.visibleViewController(from: navigationController.visibleViewController ?? navigationController.topViewController)

        case let tabBarController as UITabBarController:
            return UIWindow.visibleViewController(from: tabBarController.selectedViewController)

        case let presentingViewController where viewController?.presentedViewController != nil:
            return UIWindow.visibleViewController(from: presentingViewController?.presentedViewController)

        default:
            return viewController
        }
    }
}

La idea básica es la misma que en la respuesta de zirinisp, solo está usando una sintaxis más Swift 3+.


Uso

Probablemente quieras crear un archivo llamado UIWindowExtension.swift. Asegúrese de que incluya la import UIKitdeclaración, ahora copie el código de extensión anterior .

En el lado de la llamada, puede usarse sin ningún controlador de vista específico :

if let visibleViewCtrl = UIApplication.shared.keyWindow?.visibleViewController {
    // do whatever you want with your `visibleViewCtrl`
}

O si sabe que su controlador de vista visible es accesible desde un controlador de vista específico :

if let visibleViewCtrl = UIWindow.visibleViewController(from: specificViewCtrl) {
    // do whatever you want with your `visibleViewCtrl`
}

¡Espero que ayude!

Jeehut
fuente
El tercer caso se bloqueará debido a una recursión infinita. La solución es cambiar el nombre del vc como presentingViewControllery pasarlo presentingViewController.presentedViewControllercomo parámetro al método recursivo.
Ikhsan Assaat
No lo entendí, lo siento. ¿Quieres decir que UIWindow.visibleViewController(from: presentedViewController)debería ser UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut
correcto, presentedViewControllery viewControlleres el mismo objeto y llamará al método consigo mismo hasta que la pila se desborde (juego de palabras). Así será case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat
1
Esta solución funcionó cuando otros no. Debe actualizar a Swift 5. Esencialmente, no hay cambio. Simplemente actualice el encabezado para su respuesta.
TM Lynch
14

Descubrí que iOS 8 lo ha estropeado todo. En iOS 7 hay una nueva UITransitionViewjerarquía en la vista cada vez que tiene una presentación modal UINavigationController. De todos modos, aquí está mi código que encuentra obtiene el VC más alto. Las llamadas getTopMostViewControllerdeberían devolver un VC al que debería poder enviarle un mensaje presentViewController:animated:completion. Su propósito es obtener un VC que pueda usar para presentar un VC modal, por lo que lo más probable es que se detenga y regrese en clases de contenedor como UINavigationControllery NO el VC contenido dentro de ellos. No debería ser difícil adaptar el código para hacerlo también. He probado este código en varias situaciones en iOS 6, 7 y 8. Avísame si encuentras errores.

+ (UIViewController*) getTopMostViewController
{
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    if (window.windowLevel != UIWindowLevelNormal) {
        NSArray *windows = [[UIApplication sharedApplication] windows];
        for(window in windows) {
            if (window.windowLevel == UIWindowLevelNormal) {
                break;
            }
        }
    }

    for (UIView *subView in [window subviews])
    {
        UIResponder *responder = [subView nextResponder];

        //added this block of code for iOS 8 which puts a UITransitionView in between the UIWindow and the UILayoutContainerView
        if ([responder isEqual:window])
        {
            //this is a UITransitionView
            if ([[subView subviews] count])
            {
                UIView *subSubView = [subView subviews][0]; //this should be the UILayoutContainerView
                responder = [subSubView nextResponder];
            }
        }

        if([responder isKindOfClass:[UIViewController class]]) {
            return [self topViewController: (UIViewController *) responder];
        }
    }

    return nil;
}

+ (UIViewController *) topViewController: (UIViewController *) controller
{
    BOOL isPresenting = NO;
    do {
        // this path is called only on iOS 6+, so -presentedViewController is fine here.
        UIViewController *presented = [controller presentedViewController];
        isPresenting = presented != nil;
        if(presented != nil) {
            controller = presented;
        }

    } while (isPresenting);

    return controller;
}
nvrtd primero
fuente
No duplique las respuestas: marque las preguntas como duplicadas si es así o responda las preguntas individuales con la respuesta específica que se merecen si no son duplicadas.
Flexo
13

Mucho menos código que todas las demás soluciones:

Versión Objective-C:

- (UIViewController *)getTopViewController {
    UIViewController *topViewController = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    while (topViewController.presentedViewController) topViewController = topViewController.presentedViewController;

    return topViewController;
}

Versión Swift 2.0: (el crédito va para Steve.B)

func getTopViewController() -> UIViewController {
    var topViewController = UIApplication.sharedApplication().delegate!.window!!.rootViewController!
    while (topViewController.presentedViewController != nil) {
        topViewController = topViewController.presentedViewController!
    }
    return topViewController
}

Funciona en cualquier lugar de su aplicación, incluso con modales.

jungledev
fuente
1
Esto no maneja la situación en la que el controlador de vista presentado es uno UINavigationControllerque tiene sus propios hijos.
levigroker
@levigroker, ¿tal vez es la forma en que has diseñado tus vistas? Funciona bien para mí usar esto con un Nav. (así es como lo estoy usando)
jungledev
@jungledev Estoy seguro de que tienes razón. Dicho esto, lo que se necesita es una solución que funcione en todas las configuraciones del controlador de vista.
levigroker
@levigroker se hace obra, en toda vc estándar Configuraciones- la obra aplicación que tiene una arquitectura de muy compleja, es utilizado por más de 500 mil usuarios, y esto funciona en todas partes en la aplicación. Tal vez debería publicar una pregunta preguntando por qué no funciona en su opinión, con ejemplos de código.
jungledev
jungledev Estoy feliz de que este código funcione para usted, pero no parece ser una solución completa. La respuesta de @ zirinisp funciona perfectamente en mi situación.
levigroker
8

Respuesta de zirinisp en Swift:

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
Es as!y navigationController.visibleViewController!para Swift 2.0
LinusGeffarth
7

Especifique el título de cada ViewController y luego obtenga el título del ViewController actual mediante el código que se proporciona a continuación.

-(void)viewDidUnload {
  NSString *currentController = self.navigationController.visibleViewController.title;

Luego verifíquelo por su título como este

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}
Neel Kamal
fuente
Dfntly la mejor respuesta, también puede nombrar su viewController con:self.title = myPhotoView
Resty
5

¡El mío es mejor! :)

extension UIApplication {
    var visibleViewController : UIViewController? {
        return keyWindow?.rootViewController?.topViewController
    }
}

extension UIViewController {
    fileprivate var topViewController: UIViewController {
        switch self {
        case is UINavigationController:
            return (self as! UINavigationController).visibleViewController?.topViewController ?? self
        case is UITabBarController:
            return (self as! UITabBarController).selectedViewController?.topViewController ?? self
        default:
            return presentedViewController?.topViewController ?? self
        }
    }
}
Nicolas Manzini
fuente
4

¿Por qué no solo manejar el código de notificación push en el delegado de la aplicación? ¿Está directamente relacionado con una vista?

Puede verificar si la vista de UIViewController está actualmente visible al verificar si la windowpropiedad de la vista tiene un valor. Ver más aquí .

Dima
fuente
Sí, está relacionado con una vista, ya que tengo que mostrar la vista de la insignia. déjame revisar el enlace. gracias :)
lu yuan
4

Solo suma a la respuesta @zirinisp.

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

}

Gracias a @zirinisp.

Ashok
fuente
3

Con respecto a NSNotificationCenter Post anterior (lo siento, no puedo encontrar dónde publicar un comentario debajo de él ...)

En caso de que algunos recibieran el tipo de error - [NSConcreteNotification allKeys]. Cambia esto:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

a esto:

-(void)didReceiveRemoteNotification:(NSNotification*)notif {
NSDictionary *dict = notif.userInfo;
}
Bseaborn
fuente
3

Esto funcionó para mí. Tengo muchos objetivos que tienen diferentes controladores, por lo que las respuestas anteriores no parecían funcionar.

primero quieres esto dentro de tu clase AppDelegate:

var window: UIWindow?

entonces, en tu función

let navigationController = window?.rootViewController as? UINavigationController
if let activeController = navigationController!.visibleViewController {
    if activeController.isKindOfClass( MyViewController )  {
        println("I have found my controller!")    
   }
}
CodeOverRide
fuente
2

Esta es la mejor manera posible que he probado. Si debería ayudar a alguien ...

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

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

    return topController;
}
Mayur Deshmukh
fuente
2
extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
    }
}

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else {
            return self
        }
    }
}

Con esto, puede obtener fácilmente el controlador de vista superior de la publicación de esta manera

let viewController = UIApplication.topMostViewController

Una cosa a tener en cuenta es que si hay un UIAlertController que se muestra actualmente, UIApplication.topMostViewControllerdevolverá a UIAlertController.

NSExceptional
fuente
1

Versión Swift 2.0 de la respuesta de jungledev

func getTopViewController() -> UIViewController {
    var topViewController = UIApplication.sharedApplication().delegate!.window!!.rootViewController!
    while (topViewController.presentedViewController != nil) {
        topViewController = topViewController.presentedViewController!
    }
    return topViewController
}
Steven B.
fuente
1

Creé una categoría para UIApplicationcon la visibleViewControllerspropiedad. La idea principal es bastante simple. Swizzling viewDidAppeary viewDidDisappearmétodos en UIViewController. En el viewDidAppearmétodo viewController se agrega a la pila. En el viewDidDisappearmétodo viewController se elimina de la pila. NSPointerArrayse usa en lugar de NSArrayalmacenar UIViewControllerlas referencias de los débiles . Este enfoque funciona para cualquier jerarquía viewControllers.

UIApplication + VisibleViewControllers.h

#import <UIKit/UIKit.h>

@interface UIApplication (VisibleViewControllers)

@property (nonatomic, readonly) NSArray<__kindof UIViewController *> *visibleViewControllers;

@end

UIApplication + VisibleViewControllers.m

#import "UIApplication+VisibleViewControllers.h"
#import <objc/runtime.h>

@interface UIApplication ()

@property (nonatomic, readonly) NSPointerArray *visibleViewControllersPointers;

@end

@implementation UIApplication (VisibleViewControllers)

- (NSArray<__kindof UIViewController *> *)visibleViewControllers {
    return self.visibleViewControllersPointers.allObjects;
}

- (NSPointerArray *)visibleViewControllersPointers {
    NSPointerArray *pointers = objc_getAssociatedObject(self, @selector(visibleViewControllersPointers));
    if (!pointers) {
        pointers = [NSPointerArray weakObjectsPointerArray];
        objc_setAssociatedObject(self, @selector(visibleViewControllersPointers), pointers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return pointers;
}

@end

@implementation UIViewController (UIApplication_VisibleViewControllers)

+ (void)swizzleMethodWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
    BOOL didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleMethodWithOriginalSelector:@selector(viewDidAppear:)
                               swizzledSelector:@selector(uiapplication_visibleviewcontrollers_viewDidAppear:)];
        [self swizzleMethodWithOriginalSelector:@selector(viewDidDisappear:)
                               swizzledSelector:@selector(uiapplication_visibleviewcontrollers_viewDidDisappear:)];
    });
}

- (void)uiapplication_visibleviewcontrollers_viewDidAppear:(BOOL)animated {
    [[UIApplication sharedApplication].visibleViewControllersPointers addPointer:(__bridge void * _Nullable)self];
    [self uiapplication_visibleviewcontrollers_viewDidAppear:animated];
}

- (void)uiapplication_visibleviewcontrollers_viewDidDisappear:(BOOL)animated {
    NSPointerArray *pointers = [UIApplication sharedApplication].visibleViewControllersPointers;
    for (int i = 0; i < pointers.count; i++) {
        UIViewController *viewController = [pointers pointerAtIndex:i];
        if ([viewController isEqual:self]) {
            [pointers removePointerAtIndex:i];
            break;
        }
    }
    [self uiapplication_visibleviewcontrollers_viewDidDisappear:animated];
}

@end

https://gist.github.com/medvedzzz/e6287b99011f2437ac0beb5a72a897f0

Versión Swift 3

UIApplication + VisibleViewControllers.swift

import UIKit

extension UIApplication {

    private struct AssociatedObjectsKeys {
        static var visibleViewControllersPointers = "UIApplication_visibleViewControllersPointers"
    }

    fileprivate var visibleViewControllersPointers: NSPointerArray {
        var pointers = objc_getAssociatedObject(self, &AssociatedObjectsKeys.visibleViewControllersPointers) as! NSPointerArray?
        if (pointers == nil) {
            pointers = NSPointerArray.weakObjects()
            objc_setAssociatedObject(self, &AssociatedObjectsKeys.visibleViewControllersPointers, pointers, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        return pointers!
    }

    var visibleViewControllers: [UIViewController] {
        return visibleViewControllersPointers.allObjects as! [UIViewController]
    }
}

extension UIViewController {

    private static func swizzleFunc(withOriginalSelector originalSelector: Selector, swizzledSelector: Selector) {
        let originalMethod = class_getInstanceMethod(self, originalSelector)
        let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
        let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
        if didAddMethod {
            class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }

    override open class func initialize() {
        if self != UIViewController.self {
            return
        }
        let swizzlingClosure: () = {
            UIViewController.swizzleFunc(withOriginalSelector: #selector(UIViewController.viewDidAppear(_:)),
                                         swizzledSelector: #selector(uiapplication_visibleviewcontrollers_viewDidAppear(_:)))
            UIViewController.swizzleFunc(withOriginalSelector: #selector(UIViewController.viewDidDisappear(_:)),
                                         swizzledSelector: #selector(uiapplication_visibleviewcontrollers_viewDidDisappear(_:)))
        }()
        swizzlingClosure
    }

    @objc private func uiapplication_visibleviewcontrollers_viewDidAppear(_ animated: Bool) {
        UIApplication.shared.visibleViewControllersPointers.addPointer(Unmanaged.passUnretained(self).toOpaque())
        uiapplication_visibleviewcontrollers_viewDidAppear(animated)
    }

    @objc private func uiapplication_visibleviewcontrollers_viewDidDisappear(_ animated: Bool) {
        let pointers = UIApplication.shared.visibleViewControllersPointers
        for i in 0..<pointers.count {
            if let pointer = pointers.pointer(at: i) {
                let viewController = Unmanaged<AnyObject>.fromOpaque(pointer).takeUnretainedValue() as? UIViewController
                if viewController.isEqual(self) {
                    pointers.removePointer(at: i)
                    break
                }
            }
        }
        uiapplication_visibleviewcontrollers_viewDidDisappear(animated)
    }
}

https://gist.github.com/medvedzzz/ee6f4071639d987793977dba04e11399

Evgeny Mikhaylov
fuente
1

Siempre verifique su configuración de compilación si está ejecutando su aplicación con depuración o lanzamiento.

NOTA IMPORTANTE: no puede probarlo sin ejecutar su aplicación en modo de depuración

Esta fue mi solución

Vikram Sinha
fuente