La UIViewController
actuelle à l'écran doit répondre aux notifications Push des APN en définissant certaines vues de badge. Mais comment pourrais-je obtenir la UIViewController
dans methodapplication:didReceiveRemoteNotification
: of AppDelegate.m
?
J'ai essayé d'utiliser self.window.rootViewController
pour obtenir l'affichage actuel UIViewController
; il peut s'agir d'un UINavigationViewController
ou d'un autre type de contrôleur de vue. Et je découvre que la propriété visibleViewController
de UINavigationViewController
peut être utilisée pour afficher UIViewController
à l'écran. Mais que puis-je faire si ce n’est pas une UINavigationViewController
?
Toute aide est appréciée! Le code associé est comme suit.
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
}
Vous pouvez également utiliser la variable rootViewController
lorsque votre contrôleur n'est pas une variable UINavigationController
:
UIViewController *vc = self.window.rootViewController;
Une fois que vous connaissez le contrôleur de vue racine, tout dépend de la manière 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 en dire plus.
MODIFIER:
Si vous voulez le plus haut vue (pas voir le contrôleur), 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 ...
J'aime toujours les solutions qui impliquent des catégories, car elles sont faciles à utiliser et peuvent être facilement réutilisées.
J'ai donc créé une catégorie sur UIWindow. Vous pouvez maintenant appeler visibleViewController sur UIWindow, ce qui vous permettra d'accéder au contrôleur de vue visible en effectuant une recherche dans la hiérarchie du contrôleur. Cela fonctionne si vous utilisez la navigation et/ou le contrôleur de barre d’onglet. Si vous avez un autre type de contrôleur à suggérer s'il vous plaît faites le moi savoir et je peux 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 rapide
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
}
}
}
}
Vous pouvez également envoyer une notification via NSNotificationCenter. Cela vous permet de gérer un certain nombre de situations dans lesquelles la hiérarchie du contrôleur de vue peut être délicate - par exemple, lors de la présentation de modaux, 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 pouvez également utiliser cette approche pour les contrôles d'instruments devant être mis à jour à la réception d'une notification et utilisés 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.
Extension simple pour UIApplication dans Swift (se soucie même de moreNavigationController dans 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 le code épuré:
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
}
}
Voici une approche utilisant la syntaxe géniale switch-case in Swift 3 :
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, c'est simplement une syntaxe plus semblable à Swift 3.
Vous voulez probablement créer un fichier nommé UIWindowExtension.Swift
. Assurez-vous qu’il inclut l’instruction import UIKit
, maintenant copiez le code d’extension ci-dessus .
Du côté des appels, 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!
J'ai trouvé que iOS 8 a tout bousillé. Dans iOS 7, il existe une nouvelle UITransitionView
dans la hiérarchie des vues chaque fois que vous avez une version modale UINavigationController
. Quoi qu'il en soit, voici mon code qui trouve le meilleur CV. L'appel de getTopMostViewController
devrait renvoyer un VC que vous devriez pouvoir envoyer un message tel que presentViewController:animated:completion
. Son but est de vous fournir un VC que vous pouvez utiliser pour présenter un VC modal, afin qu'il puisse très probablement s'arrêter et revenir aux classes de conteneur telles que UINavigationController
et PAS le VC qu'elles contiennent. 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. Merci de me prévenir 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;
}
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: (merci à 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.
Spécifiez le titre de chaque ViewController, puis obtenez le titre du ViewController actuel à l'aide du code indiqué ci-dessous.
-(void)viewDidUnload {
NSString *currentController = self.navigationController.visibleViewController.title;
Puis vérifiez par votre titre comme ça
if([currentController isEqualToString:@"myViewControllerTitle"]){
//write your code according to View controller.
}
}
zirinisp's Answer in 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)
}
Pourquoi ne pas simplement gérer le code de notification Push dans le délégué de l'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 propriété window
de la vue a une valeur. Voir plus ici .
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
}
}
}
Juste addition à @zirinisp répondre.
Créez un fichier, nommez-le UIWindowExtension.Swift
et 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.
En ce qui concerne NSNotificationCenter Post ci-dessus (désolé de ne pas savoir où poster un commentaire en dessous ...)
Dans le cas où certains recevaient l'erreur de type - [NSConcreteNotification allKeys]. Change ça:
-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo
pour ça:
-(void)didReceiveRemoteNotification:(NSNotification*)notif {
NSDictionary *dict = notif.userInfo;
}
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 si
let viewController = UIApplication.topMostViewController
Une chose à noter est que si un UIAlertController est actuellement affiché, UIApplication.topMostViewController
renverra une UIAlertController
.
Cela a fonctionné pour moi. De nombreuses cibles ayant différents contrôleurs, les réponses précédentes ne semblaient pas fonctionner.
d'abord, vous voulez ceci dans votre classe AppDelegate:
var window: UIWindow?
alors, dans votre fonction
let navigationController = window?.rootViewController as? UINavigationController
if let activeController = navigationController!.visibleViewController {
if activeController.isKindOfClass( MyViewController ) {
println("I have found my controller!")
}
}
C'est la meilleure façon possible que j'ai essayée. Si cela peut aider quelqu'un ...
+ (UIViewController*) topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
Vérifiez toujours votre configuration de construction si vous exécutez votre application avec debug ou release.
REMARQUE IMPORTANTE: vous ne pouvez pas le tester sans exécuter votre application en mode débogage
C'était ma solution
Swift 2.0 version 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
}
J'ai créé une catégorie pour UIApplication
avec la propriété visibleViewControllers
. L'idée principale est assez simple. J'ai passé en revue les méthodes viewDidAppear
et viewDidDisappear
dans UIViewController
. Dans la méthode viewDidAppear
, viewController est ajouté à la pile. Dans viewDidDisappear
, la méthode viewController est retirée de la pile. NSPointerArray
est utilisé au lieu de NSArray
pour stocker les références faibles de UIViewController
. Cette approche fonctionne pour toute hiérarchie de 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