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

}

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


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

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

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.


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!


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

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.


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

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

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

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 .


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.


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

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!")    
   }
}

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

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.


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
}

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


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.