Obtenez le UIViewController d'affichage actuel sur l'écran dans AppDelegate.m

126

Le courant UIViewControllerà l'écran doit répondre aux notifications push des APN, en définissant certaines vues de badge. Mais comment pourrais-je obtenir la UIViewControllerméthode in application:didReceiveRemoteNotification: of AppDelegate.m?

J'ai essayé d'utiliser self.window.rootViewControllerpour obtenir l'affichage actuel UIViewController, cela peut être un UINavigationViewControllerou un autre type de contrôleur de vue. Et je découvre que la visibleViewControllerpropriété de UINavigationViewControllerpeut être utilisée pour obtenir le UIViewControllersur l'écran. Mais que pourrais-je faire si ce n'est pas unUINavigationViewController ?

Toute aide est appréciée! Le code associé est le suivant.

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
la source

Réponses:

99

Vous pouvez rootViewControllerégalement utiliser le lorsque votre contrôleur n'est pas un UINavigationController:

UIViewController *vc = self.window.rootViewController;

Une fois que vous connaissez le contrôleur de vue racine, cela dépend de la façon dont vous avez construit votre interface utilisateur, mais vous pouvez éventuellement trouver un moyen de naviguer dans la hiérarchie des contrôleurs.

Si vous donnez plus de détails sur la façon dont vous avez défini votre application, je pourrais peut-être vous donner plus d'indices.

ÉDITER:

Si vous voulez la vue la plus haute (pas le contrôleur de vue), vous pouvez vérifier

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

bien que cette vue puisse être invisible ou même couverte par certaines de ses sous-vues ...

encore une fois, cela dépend de votre interface utilisateur, mais cela pourrait aider ...

Sergio
la source
19
Le problème avec ceci est si la vue visible n'appartient pas au contrôleur de vue racine (dans le cas des vues modales et autres).
Dima
Oui. Mais c'est peut-être un UITabViewController. N'existe-t-il pas une méthode directe pour afficher UIViewController à l'écran?
lu yuan
2
Eh bien, vous voyez, UINavigationController vous permet de savoir quel contrôleur est le plus haut; votre contrôleur racine devrait fournir les mêmes informations d'une manière ou d'une autre. Cela ne peut pas être déduit en général car cela dépend strictement de la façon dont vous avez construit votre interface utilisateur et il n'y a pas de hiérarchie de contrôleur explicite (comme cela se produit pour les vues). Vous pouvez simplement ajouter une propriété à votre contrôleur racine et définir sa valeur chaque fois que vous "poussez" un nouveau contrôleur sur le dessus.
sergio
1
Tant que la valeur est maintenue à jour, cela me semble également être une bonne façon de procéder.
Dima
4
Il n'existe aucun moyen direct d'accéder au contrôleur à partir d'une UIViewinstance. rootViewControllern'est pas nécessairement le contrôleur actuellement affiché. C'est juste en haut de la hiérarchie des vues.
Gingi
101

J'aime toujours les solutions qui impliquent des catégories car elles sont boulonnées et peuvent être facilement réutilisées.

J'ai donc créé une catégorie sur UIWindow. Vous pouvez maintenant appeler visibleViewController sur UIWindow et cela vous donnera le contrôleur de vue visible en recherchant dans la hiérarchie du contrôleur. Cela fonctionne si vous utilisez la navigation et / ou le contrôleur de barre d'onglets. Si vous avez un autre type de contrôleur à suggérer, faites-le moi savoir et je pourrai l'ajouter.

UIWindow + PazLabs.h (fichier d'en-tête)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (fichier d'implémentation)

#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

Version Swift

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
la source
2
comment puis-je l'utiliser pour la version swift?
Vijay Singh Rana
2
Je ne comprends pas votre question. Copiez et collez dans votre code.
zirinisp du
Qu'en est-il du conteneur personnalisé VC?
Mingming
@Mingming, il ne devrait pas être si difficile d'ajouter un supplément pour vérifier si c'est le conteneur personnalisé VC (dans la méthode getVisibielController) et si tel est le cas, renvoyer le contrôleur "visible", qui serait généralement vc.childControllers.lastObject pour la plupart des utilisateurs Conteneur VC implémentations (je suppose), mais dépendrait de la façon dont il est implémenté.
gadu
1
Je viens de publier une réponse avec la même approche que dans cette réponse, à l'exception d'une syntaxe mise à jour: Il utilise un boîtier de commutation et suit les conventions de dénomination de Swift 3: stackoverflow.com/a/42486823/3451975
Jeehut
43

Extension simple pour UIApplication dans Swift (se soucie même de plus deNavigationController UITabBarControllersur 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
    }
}

Utilisation simple:

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

Fonctionne parfaitement :-)

MISE À JOUR pour le code propre:

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
la source
1
Cela semble être le code pour Swift 2.x. Swift 3.x n'a plus "où". De plus, "sharedApplication ()" est désormais "shared". Pas grand-chose. La mise à jour ne prend qu'une minute. Peut-être bon de mentionner qu'il utilise la récursivité. De plus, chaque appel à topViewController doit avoir besoin du préfixe "base:".
Jeff Muir
37

Vous pouvez également publier une notification via NSNotificationCenter. Cela vous permet de gérer un certain nombre de situations dans lesquelles la traversée de la hiérarchie du contrôleur de vue peut être délicate - par exemple lorsque des modaux sont présentés, etc.

Par exemple,

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

Dans chacun de vos contrôleurs d'affichage:

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

Vous pouvez également utiliser cette approche pour les commandes d'instruments qui doivent être mises à jour lorsqu'une notification est reçue et sont utilisées par plusieurs contrôleurs de vue. Dans ce cas, gérez les appels d'observateur d'ajout / suppression dans les méthodes init et dealloc, respectivement.

Aneil Mallavarapu
la source
1
Qu'y a-t-il à l' addObserver:barintérieur viewDidLoad? Dois-je remplacer par self?
CainaSouza
Merci de l'avoir signalé - ça devrait être le soi Je mettrai à jour la réponse.
Aneil Mallavarapu
crash lors de l'obtention de toutes les clés de userInfo .. Une idée? [NSConcreteNotification allKeys]: sélecteur non reconnu envoyé à l'instance 0x1fd87480 05/07/2013 16: 10: 36.469 Providence [2961: 907] *** Arrêt de l'application en raison d'une exception non interceptée 'NSInvalidArgumentException', raison: '- [NSConcreteNotification allKeys]: non reconnu sélecteur envoyé à l'instance 0x1fd87480 '
Awais Tariq
@AwaisTariq - Hmmm - je suppose que l'objet passé par iOS à didReceiveRemoteNotification n'est pas en fait un NSDictionary, comme le spécifie l'interface.
Aneil Mallavarapu
Que faire si l'utilisateur n'a pas encore navigué vers votre classe d'observateur? : /
halbano
15

Code

Voici une approche utilisant la grande syntaxe de cas de commutation dans 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
        }
    }
}

L'idée de base est la même que dans la réponse de zirinisp, il utilise simplement une syntaxe plus Swift 3+.


Usage

Vous souhaitez probablement créer un fichier nommé UIWindowExtension.swift. Assurez-vous qu'il inclut l' import UIKitinstruction, copiez maintenant le code d'extension ci-dessus .

Du côté appel, il peut être utilisé sans contrôleur de vue spécifique :

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

Ou si vous savez que votre contrôleur de vue visible est accessible à partir d'un contrôleur de vue spécifique :

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

J'espère que ça aide!

Jeehut
la source
Le troisième cas plantera à cause d'une récursion infinie. Le correctif consiste à renommer le vc presentingViewControlleret à le passer presentingViewController.presentedViewControllercomme paramètre à la méthode récursive.
Ikhsan Assaat
Je n'ai pas tout à fait compris, désolé. Vous voulez dire que UIWindow.visibleViewController(from: presentedViewController)devrait plutôt l'être UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut
correct, presentedViewControlleret viewControllerc'est le même objet et il appellera la méthode avec lui-même jusqu'à ce que la pile déborde (jeu de mots prévu). Alors ça va être case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat
1
Cette solution a fonctionné lorsque d'autres n'ont pas fonctionné. Vous devez mettre à jour vers Swift 5. Essentiellement aucun changement. Mettez simplement à jour l'en-tête de votre réponse.
TM Lynch
14

J'ai constaté qu'iOS 8 a tout gâché. Dans iOS 7, il y a un nouveau UITransitionViewsur la hiérarchie des vues chaque fois que vous avez une présentation modale UINavigationController. Quoi qu'il en soit, voici mon code qui trouve obtient le VC le plus élevé. L'appel getTopMostViewControllerdevrait renvoyer un VC auquel vous devriez pouvoir envoyer un message comme presentViewController:animated:completion. Son but est de vous obtenir un VC que vous pouvez utiliser pour présenter un VC modal, donc il s'arrêtera probablement et retournera aux classes de conteneurs comme UINavigationControlleret NON au VC qu'ils contiennent. Il ne devrait pas être difficile d'adapter le code pour le faire aussi. J'ai testé ce code dans diverses situations sous iOS 6, 7 et 8. Veuillez me faire savoir si vous trouvez des bogues.

+ (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 d'abord
la source
Veuillez ne pas dupliquer les réponses - marquez les questions comme des doublons si elles le sont, ou répondez aux questions individuelles avec la réponse spécifique qu'elles méritent si elles ne sont pas des doublons.
Flexo
13

Beaucoup moins de code que toutes les autres solutions:

Version Objective-C:

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

    return topViewController;
}

Version Swift 2.0: (le mérite revient à Steve.B)

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

Fonctionne n'importe où dans votre application, même avec des modaux.

jungledev
la source
1
Cela ne gère pas la situation où le contrôleur de vue présenté est un UINavigationControllerqui a ses propres enfants.
levigroker
@levigroker, c'est peut-être la façon dont vous avez conçu votre point de vue? Cela fonctionne bien pour moi d'utiliser cela avec un Nav. (c'est comme ça que je l'utilise)
jungledev
@jungledev Je suis sûr que vous avez raison. Cela dit, une solution qui fonctionne dans toutes les configurations de contrôleur de vue est ce qu'il faut.
levigroker
@levigroker il fait le travail dans tous les vc standards Configurations- l'application que je travaille sur a une architecture très complexe, est utilisé par plus de 500k utilisateurs, et cela fonctionne partout dans l'application. Peut-être devriez-vous publier une question demandant pourquoi cela ne fonctionne pas dans votre vue, avec des exemples de code?
jungledev
jungledev Je suis heureux que ce code fonctionne pour vous, mais il ne semble pas être une solution complète. La réponse de @ zirinisp fonctionne parfaitement dans ma situation.
levigroker
8

Réponse de zirinisp dans 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;
            }
        }
    }
}

Usage:

 if let topController = window.visibleViewController() {
            println(topController)
        }
Bobj-C
la source
C'est as!et navigationController.visibleViewController!pour Swift 2.0
LinusGeffarth
7

Spécifiez le titre de chaque ViewController, puis obtenez le titre du ViewController actuel à l'aide du code ci-dessous.

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

Ensuite, vérifiez-le par votre titre comme ceci

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}
Neel Kamal
la source
Dfntly la meilleure réponse, vous pouvez également nommer votre viewController avec:self.title = myPhotoView
Resty
5

Le mien est meilleur! :)

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
la source
4

Pourquoi ne pas simplement gérer le code de notification push dans le délégué d'application? Est-ce directement lié à une vue?

Vous pouvez vérifier si la vue d'un UIViewController est actuellement visible en vérifiant si sa windowpropriété de vue a une valeur. En savoir plus ici .

Dima
la source
Oui, c'est lié à une vue, car je dois afficher la vue du badge. laissez-moi vérifier le lien. merci :)
lu yuan
4

Juste un ajout à la réponse @zirinisp.

Créez un fichier, nommez-le UIWindowExtension.swiftet collez l'extrait suivant:

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
}

Utilisez-le n'importe où comme:

if let topVC = getTopViewController() {

}

Merci à @zirinisp.

Ashok
la source
3

Concernant NSNotificationCenter Post ci-dessus (désolé, je ne peux pas savoir où publier un commentaire en dessous ...)

Au cas où certains obtiendraient l'erreur - [NSConcreteNotification allKeys] de toutes sortes. Change ça:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

pour ça:

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

Cela a fonctionné pour moi. J'ai de nombreuses cibles qui ont des contrôleurs différents, donc les réponses précédentes ne semblaient pas fonctionner.

vous voulez d'abord ceci dans votre classe AppDelegate:

var window: UIWindow?

puis, dans ta fonction

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

C'est la meilleure façon possible que j'ai essayée. Si cela devait aider quelqu'un ...

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

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

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

Avec cela, vous pouvez facilement obtenir le contrôleur de vue post supérieur comme ceci

let viewController = UIApplication.topMostViewController

Une chose à noter est que si un UIAlertController est actuellement affiché, UIApplication.topMostViewControllerretournera un fichier UIAlertController.

NSExceptionnel
la source
1

Version Swift 2.0 de la réponse de Jungledev

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

J'ai créé une catégorie pour UIApplicationavec visibleViewControllerspropriété. L'idée principale est assez simple. J'ai swizzed viewDidAppearet les viewDidDisappearméthodes dedans UIViewController. Dans la viewDidAppearméthode, viewController est ajouté à la pile. Dans la viewDidDisappearméthode, viewController est supprimé de la pile. NSPointerArrayest utilisé au lieu de NSArraypour stocker faibleUIViewController les références . Cette approche fonctionne pour n'importe quelle hiérarchie 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

Version 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
la source
1

Vérifiez toujours la configuration de votre build si vous exécutez votre application avec le débogage ou la version.

REMARQUE IMPORTANTE: vous ne pouvez pas le tester sans exécuter votre application en mode débogage

C'était ma solution

Vikram Sinha
la source