137 votes

Obtenir le UIViewController actuellement affiché à l'écran dans AppDelegate.m

L'actuel UIViewController sur l'écran doivent répondre aux notifications push des APN, en définissant des vues de badges. Mais comment puis-je obtenir le UIViewController en méthode application:didReceiveRemoteNotification : de AppDelegate.m ?

J'ai essayé d'utiliser self.window.rootViewController pour obtenir l'affichage actuel UIViewController il peut s'agir d'un UINavigationViewController ou un autre type de contrôleur de vue. Et je découvre que le visibleViewController propriété de UINavigationViewController peut être utilisé pour obtenir le UIViewController sur l'écran. Mais que faire s'il ne s'agit pas d'une UINavigationViewController ?

Toute aide est appréciée ! Le code correspondant 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

}

111voto

zirinisp Points 984

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 cherchant dans la hiérarchie des contrôleurs. Cela fonctionne si vous utilisez un contrôleur de navigation et/ou 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
            }
        }
    }
}

105voto

sergio Points 52422

Vous pouvez utiliser le rootViewController également 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 pourrai vous donner des indications supplémentaires.

EDITAR:

Si vous voulez que le plus haut voir (pas le contrôleur de vue), vous pourriez 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 vous aider...

49voto

Extension simple pour UIApplication en Swift (se préoccupe même de moreNavigationController au sein de UITabBarController sur 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 :-)

UPDATE pour un 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
    }
}

38voto

Aneil Mallavarapu Points 1074

Vous pouvez également envoyer une notification via le NSNotificationCenter. Cela vous permet de faire face à un certain nombre de situations où traverser la hiérarchie du contrôleur de vue peut être délicat, par exemple lorsque des modales sont présentées, 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 de vue :

-(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 pourriez également utiliser cette approche pour instrumenter les contrôles qui doivent être mis à jour lorsqu'une notification est reçue et qui sont utilisés par plusieurs contrôleurs de vue. Dans ce cas, traitez les appels d'observateurs d'ajout et de suppression dans les méthodes init et dealloc, respectivement.

25voto

Dschee Points 8441

Code

Voici une approche utilisant le grand syntaxe de l'alternance en Swift 3/4/5 :

import UIKit

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 s'agit simplement d'utiliser une syntaxe plus proche de Swift 3+.


Utilisation

Vous voulez probablement créer un fichier nommé UIWindowExt.swift y copier le code d'extension ci-dessus en elle.

Du côté de l'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 joignable à 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 cela vous aidera !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X