Obtenha o UIViewController de exibição atual na tela em AppDelegate.m

126

A corrente UIViewControllerna tela precisa responder a notificações push de APNs, definindo algumas visualizações de emblema. Mas como eu poderia obter o UIViewControllermétodo in application:didReceiveRemoteNotification: of AppDelegate.m?

Eu tentei usar self.window.rootViewControllerpara obter a exibição atual UIViewController, pode ser um UINavigationViewControllerou outro tipo de controlador de exibição. E eu descobri que a visibleViewControllerpropriedade de UINavigationViewControllerpode ser usada para UIViewControllerexibir a tela. Mas o que eu poderia fazer se não fosse um UINavigationViewController?

Qualquer ajuda é apreciada! O código relacionado é o seguinte.

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
fonte

Respostas:

99

Você também pode usar o rootViewControllerquando seu controlador não for UINavigationController:

UIViewController *vc = self.window.rootViewController;

Depois de conhecer o controlador de visualização raiz, isso depende de como você construiu sua interface do usuário, mas é possível descobrir uma maneira de navegar pela hierarquia de controladores.

Se você fornecer mais detalhes sobre como definiu seu aplicativo, talvez eu dê mais algumas dicas.

EDITAR:

Se você deseja a vista superior (não a controladora), verifique

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

embora essa visão possa ser invisível ou mesmo coberta por algumas de suas subvisões ...

novamente, depende da sua interface do usuário, mas isso pode ajudar ...

sergio
fonte
19
O problema é se a visualização visível não pertence ao controlador de visualização raiz (no caso de visualizações modais e outras).
Dima
Sim eu quero. Mas talvez seja um UITabViewController. Não existe um método direto para obter o UIViewController na tela?
lu yuan
2
bem, você vê, o UINavigationController fornece uma maneira de você saber qual controlador está no topo; seu controlador raiz deve fornecer as mesmas informações de alguma forma. Ele não pode ser inferido em geral porque depende estritamente de como você construiu sua interface do usuário e não há hierarquia explícita de controlador (como acontece nas visualizações). Você pode simplesmente adicionar uma propriedade ao seu controlador raiz e definir seu valor sempre que "empurra" um novo controlador por cima.
sergio
1
Contanto que o valor seja mantido atualizado, isso também parece ser uma boa maneira de mim.
Dima
4
Não existe uma maneira direta de acessar o controlador a partir de uma UIViewinstância. nãorootViewController é necessariamente o controlador atualmente exibido. É apenas no topo da hierarquia de visualizações.
Gingi
101

Eu sempre adoro soluções que envolvem categorias, pois são fáceis de serem reutilizadas.

Então, eu criei uma categoria no UIWindow. Agora você pode chamar visibleViewController no UIWindow, e você obterá o controlador de exibição visível pesquisando a hierarquia do controlador. Isso funciona se você estiver usando o controle de navegação e / ou barra de guias. Se você tiver outro tipo de controlador para sugerir, entre em contato e posso adicioná-lo.

UIWindow + PazLabs.h (arquivo de cabeçalho)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (arquivo de implementação)

#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

Versão 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
fonte
2
como posso usar isso para a versão rápida?
Vijay Singh Rana
2
Não consigo entender sua pergunta. Copie e cole dentro do seu código.
Zirinisp
E o contêiner personalizado VC?
Mingming
@Mingming não deve ser tão difícil adicionar um extra se verificar se é o contêiner personalizado VC (no método getVisibielController) e, em caso afirmativo, retornar o controlador "visible", que normalmente seria vc.childControllers.lastObject para a maioria dos implementações de contêineres VC (suponho), mas dependeria de como é implementado.
gadu
1
Acabei de publicar uma resposta com a mesma abordagem que nesta resposta, exceto para uma sintaxe atualização: Ele está usando um caso de switch e segue as Swift 3 convenções de nomenclatura: stackoverflow.com/a/42486823/3451975
Jeehut
43

Extensão simples para UIApplication no Swift (preocupa-se ainda mais com o Controlador de Navegação UITabBarControllerno 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 simples:

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

Works perfect :-)

ATUALIZAÇÃO para código limpo:

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
fonte
1
Este parece ser o código para o Swift 2.x. O Swift 3.x não tem mais "onde". Além disso, "sharedApplication ()" agora é "shared". Não é grande coisa. Leva apenas um minuto para atualizar. Pode ser bom mencionar que ele usa recursão. Além disso, cada chamada para topViewController deve precisar do prefixo "base:".
Jeff Muir
37

Você também pode postar uma notificação via NSNotificationCenter. Isso permite que você lide com várias situações em que percorrer a hierarquia do controlador de exibição pode ser complicado - por exemplo, quando os modais estão sendo apresentados, etc.

Por exemplo,

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

Em cada um dos seus Controladores de exibição:

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

Você também pode usar essa abordagem para instrumentar controles que precisam ser atualizados quando uma notificação é recebida e são usados ​​por vários controladores de exibição. Nesse caso, lide com as chamadas de observação de adição / remoção nos métodos init e dealloc, respectivamente.

Aneil Mallavarapu
fonte
1
O que tem addObserver:bardentro viewDidLoad? Eu tenho que substituir self?
CainaSouza
Obrigado por apontar isso - deve ser auto. Vou atualizar a resposta.
Aneil Mallavarapu
travar ao obter todas as chaves do userInfo. Alguma idéia? [NSConcreteNotification allKeys]: seletor não reconhecido enviado para a instância 0x1fd87480 2013-07-05 16: 10: 36.469 Providence [2961: 907] *** Finalizando o aplicativo devido à exceção não capturada 'NSInvalidArgumentException', motivo: '- [NSConcreteNotification allKeys]: não reconhecido seletor enviado para a instância 0x1fd87480 '
Awais Tariq 05/07
@AwaisTariq - Hmmm - meu palpite é que o objeto passado pelo iOS para didReceiveRemoteNotification não é realmente um NSDictionary, como a interface especifica.
Aneil Mallavarapu
E se o usuário ainda não navegou para sua classe de observador? : /
halbano
15

Código

Aqui está uma abordagem usando a excelente sintaxe de caso de switch no 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
        }
    }
}

A idéia básica é a mesma da resposta do zirinisp, é apenas usar uma sintaxe mais semelhante ao Swift 3+.


Uso

Você provavelmente deseja criar um arquivo chamado UIWindowExtension.swift. Certifique-se de que inclui a import UIKitinstrução, agora copie o código de extensão acima .

No lado da chamada, ele pode ser usado sem nenhum controlador de exibição específico :

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

Ou, se você sabe que seu controlador de vista visível está acessível a partir de um controlador de vista específico :

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

Espero que ajude!

Jeehut
fonte
O terceiro caso falhará devido a uma recursão infinita. A correção é renomear vc como presentingViewControllere passar presentingViewController.presentedViewControllercomo parâmetro para o método recursivo.
Ikhsan Assaat
Eu não entendi direito, desculpe. Você quer dizer que UIWindow.visibleViewController(from: presentedViewController)deveria ser UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut 8/17/17
correto presentedViewControllere viewControlleré o mesmo objeto e ele chamará o método por si mesmo até a pilha estourar (trocadilho intencional). Assim será case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat 9/17/17
1
Esta solução funcionou quando outros não. Você deve atualizar para o Swift 5. Essencialmente, nenhuma alteração. Basta atualizar o cabeçalho da sua resposta.
TM Lynch
14

Eu descobri que o iOS 8 estragou tudo. No iOS 7, há um novo UITransitionViewna hierarquia de visualizações sempre que você faz uma apresentação modal UINavigationController. De qualquer forma, aqui está o meu código que encontra o mais alto VC. A chamada getTopMostViewControllerdeve retornar um VC do qual você poderá enviar uma mensagem como presentViewController:animated:completion. Seu objetivo é obter um VC que você possa usar para apresentar um VC modal; portanto, ele provavelmente irá parar e retornar em classes de contêiner como UINavigationControllere NÃO no VC contido nelas. Não deve ser difícil adaptar o código para fazer isso também. Testei esse código em várias situações no iOS 6, 7 e 8. Informe-me se encontrar bugs.

+ (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 frst
fonte
Não duplique as respostas - sinalize as perguntas como duplicadas, se houver, ou responda às perguntas individuais com a resposta específica que eles merecem, se não estiverem duplicados.
Flexo
13

Muito menos código do que todas as outras soluções:

Versão Objective-C:

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

    return topViewController;
}

Versão Swift 2.0: (crédito vai para Steve.B)

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

Funciona em qualquer lugar do seu aplicativo, mesmo com modais.

jungledev
fonte
1
Isso não lida com a situação em que o controlador de exibição apresentado é aquele UINavigationControllerque tem seus próprios filhos.
precisa saber é o seguinte
@ Levigroker, talvez seja a maneira como você arquitetou seus pontos de vista? Funciona bem para mim usar isso com um Nav. (é assim que eu estou usando-o)
jungledev
@jungledev Tenho certeza que você está correto. Dito isto, é necessária uma solução que funcione em todas as configurações de controladores de exibição.
levigroker
@levigroker que faz o trabalho em todos vc padrão configurations- o aplicativo que eu trabalho em tem uma arquitetura muito complexa, é usado por mais de 500 mil usuários, e isso funciona em todo o aplicativo. Talvez você deva postar uma pergunta perguntando por que ela não funciona na sua opinião, com exemplos de código?
jungledev
jungledev Estou feliz que este código funcione para você, mas não parece ser uma solução completa. A resposta de @ zirinisp funciona perfeitamente na minha situação.
levigroker
8

Resposta de zirinisp em 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
fonte
É as!e navigationController.visibleViewController!para Swift 2.0
LinusGeffarth 8/15/15
7

Especifique o título para cada ViewController e obtenha o título do ViewController atual pelo código fornecido abaixo.

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

Em seguida, verifique pelo seu título como este

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}
Neel Kamal
fonte
Dfntly a melhor resposta, também u pode nomear seu viewController com:self.title = myPhotoView
Resty
5

O meu é melhor! :)

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

Por que não apenas manipular o código de notificação por push no delegado do aplicativo? Está diretamente relacionado a uma visualização?

Você pode verificar se a visualização de um UIViewController está visível no momento, verificando se a windowpropriedade da visualização possui um valor. Veja mais aqui .

Dima
fonte
Sim, está relacionado a uma exibição, pois tenho que mostrar a exibição do emblema. deixe-me verificar o link. obrigado :)
lu yuan
4

Apenas adição à resposta @zirinisp.

Crie um arquivo, nomeie-o UIWindowExtension.swifte cole o seguinte trecho:

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
}

Use-o em qualquer lugar como:

if let topVC = getTopViewController() {

}

Obrigado a @zirinisp.

Ashok
fonte
3

Em relação à postagem do NSNotificationCenter acima (não é possível descobrir onde postar um comentário) ...

No caso de alguns estarem recebendo o tipo de erro - [NSConcreteNotification allKeys]. Mude isso:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

para isso:

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

Isso funcionou para mim. Como tenho muitos alvos com controladores diferentes, as respostas anteriores não pareciam funcionar.

primeiro você quer isso dentro da sua classe AppDelegate:

var window: UIWindow?

então, na sua função

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

Esta é a melhor maneira possível que eu tentei. Se isso deve ajudar alguém ...

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

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

    return topController;
}
Mayur Deshmukh
fonte
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
        }
    }
}

Com isso, você pode facilmente obter o controlador de exibição superior da postagem assim

let viewController = UIApplication.topMostViewController

Uma coisa a notar é que, se houver um UIAlertController atualmente sendo exibido, UIApplication.topMostViewControllerretornará a UIAlertController.

NSExceptional
fonte
1

Versão Swift 2.0 da resposta de jungledev

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

Eu criei uma categoria para UIApplicationcom visibleViewControllerspropriedade. A idéia principal é bem simples. I swizzled viewDidAppeare viewDidDisappearmétodos UIViewController. No viewDidAppearmétodo, viewController é adicionado à pilha. No viewDidDisappearmétodo, o viewController é removido da pilha. NSPointerArrayé usado em vez de NSArrayarmazenar UIViewControlleras referências de fraco . Essa abordagem funciona para qualquer hierarquia 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

Versão 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
fonte
1

Sempre verifique sua configuração de compilação se estiver executando seu aplicativo com depuração ou versão.

NOTA IMPORTANTE: Não é possível testá-lo sem executar o aplicativo no modo de depuração

Esta foi a minha solução

Vikram Sinha
fonte