Ottieni la visualizzazione corrente di UIViewController sullo schermo in AppDelegate.m

L’attuale UIViewController sullo schermo deve rispondere alle notifiche push dagli APN, impostando alcune viste badge. Ma come posso ottenere UIViewController nell’applicazione del metodo application:didReceiveRemoteNotification : of AppDelegate.m ?

Ho provato a utilizzare self.window.rootViewController per ottenere la visualizzazione corrente di UIViewController , potrebbe essere un UINavigationViewController o qualche altro tipo di controller di visualizzazione. E scopro che la proprietà visibleViewController di UINavigationViewController può essere utilizzata per ottenere UIViewController sullo schermo. Ma cosa potrei fare se non fosse un UINavigationViewController ?

Qualsiasi aiuto è apprezzato! Il codice relativo è il seguente.

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 } 

È ansible utilizzare rootViewController anche quando il controller non è un UINavigationController :

 UIViewController *vc = self.window.rootViewController; 

Una volta che conosci il controller della vista radice, dipende da come hai costruito l’interfaccia utente, ma puoi trovare un modo per navigare nella gerarchia dei controller.

Se fornisci ulteriori dettagli sul modo in cui hai definito la tua app, potrei dare qualche suggerimento in più.

MODIFICARE:

Se vuoi la vista più in alto (non vedere il controller), puoi controllare

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

anche se questa vista potrebbe essere invisibile o addirittura coperta da alcune delle sue sottoview …

di nuovo, dipende dalla tua interfaccia utente, ma questo potrebbe aiutare …

Amo sempre le soluzioni che coinvolgono categorie in quanto sono imbullonate e possono essere facilmente riutilizzate.

Così ho creato una categoria su UIWindow. Adesso puoi chiamare visibleViewController su UIWindow e questo ti farà vedere il controller della vista cercando la gerarchia del controller. Funziona se si utilizza il controller di navigazione e / o barra delle linguette. Se hai un altro tipo di controller da suggerire per favore fammelo sapere e posso aggiungerlo.

UIWindow + PazLabs.h (file di intestazione)

 #import  @interface UIWindow (PazLabs) - (UIViewController *) visibleViewController; @end 

UIWindow + PazLabs.m (file di implementazione)

 #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 

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

È anche ansible inviare una notifica tramite NSNotificationCenter. Questo ti permette di affrontare una serie di situazioni in cui attraversare la gerarchia del controller di visualizzazione potrebbe essere complicato, ad esempio quando vengono presentate le modali, ecc.

Per esempio,

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

In ciascuno dei tuoi View Controller:

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

È inoltre ansible utilizzare questo approccio per i controlli dello strumento che devono essere aggiornati quando viene ricevuta una notifica e utilizzati da più controller di visualizzazione. In tal caso, gestisci le chiamate aggiungi / rimuovi observer rispettivamente nei metodi init e dealloc.

Semplice estensione per UIApplication in Swift (si occupa anche di piùNavigationController all’interno di UITabBarController su 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 semplice:

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

Funziona perfettamente 🙂

Codice

Ecco un approccio che utilizza la grande syntax 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’idea di base è la stessa della risposta di zirinisp, è solo usando una syntax più simile a Swift 3.


uso

Probabilmente vorresti creare un file chiamato UIWindowExtension.swift . Assicurati che includa l’istruzione import UIKit , ora copia il codice di estensione sopra .

Dal lato della chiamata può essere utilizzato senza uno specifico controller di visualizzazione :

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

O se sai che il tuo controller di visualizzazione visibile è raggiungibile da uno specifico controller di visualizzazione :

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

Spero possa essere d’aiuto!

Ho scoperto che iOS 8 ha rovinato tutto. In iOS 7 c’è un nuovo UITransitionView sulla gerarchia delle viste ogni volta che si presenta un UINavigationController presentato in modo modale. Ad ogni modo, ecco il mio codice che trova il VC più in alto. Chiamare getTopMostViewController dovrebbe restituire un VC che dovrebbe essere in grado di inviare un messaggio come presentViewController:animated:completion . Lo scopo è quello di ottenere un VC che è ansible utilizzare per presentare un VC modale, quindi molto probabilmente si fermerà e tornerà a classi contenitore come UINavigationController e NON il VC contenuto al loro interno. Non dovrebbe essere difficile adattare il codice per farlo anche tu. Ho testato questo codice in varie situazioni in iOS 6, 7 e 8. Per favore fatemi sapere se trovate bug.

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

Meno codice di tutte le altre soluzioni:

Versione Objective-C:

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

Versione Swift 2.0: (il merito va a Steve.B)

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

Funziona ovunque nella tua app, anche con le modali.

Specificare il titolo per ogni ViewController e quindi ottenere il titolo di ViewController corrente dal codice riportato di seguito.

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

Quindi controlla il titolo come questo

  if([currentController isEqualToString:@"myViewControllerTitle"]){ //write your code according to View controller. } } 

La risposta di zirinisp 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; } } } } 

Uso:

  if let topController = window.visibleViewController() { println(topController) } 

Perché non gestire semplicemente il codice di notifica push nel delegato dell’app? È direttamente correlato a una vista?

È ansible verificare se una vista di UIViewController è attualmente visibile controllando se la proprietà della window della vista ha un valore. Vedi di più qui .

Per quanto riguarda NSNotificationCenter Post in alto (non riesco a trovare dove pubblicare un commento sotto …)

Nel caso in cui alcuni ricevessero l’errore – [NSConcreteNotification allKeys] di sorta. Cambia questo:

 -(void)didReceiveRemoteNotification:(NSDictionary *)userInfo 

a questa:

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

Solo aggiunta alla risposta @zirinisp.

Crea un file, UIWindowExtension.swift e incolla lo snippet seguente:

 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 } 

Usalo ovunque come:

 if let topVC = getTopViewController() { } 

Grazie a @zirinisp.

Il mio è migliore! 🙂

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

Questo ha funzionato per me. Ho molti obiettivi che hanno controller diversi, quindi le risposte precedenti non sembravano funzionare.

per prima cosa lo vuoi all’interno della tua class AppDelegate:

 var window: UIWindow? 

quindi, nella tua funzione

 let navigationController = window?.rootViewController as? UINavigationController if let activeController = navigationController!.visibleViewController { if activeController.isKindOfClass( MyViewController ) { println("I have found my controller!") } } 
 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 } } } 

Con questo si può facilmente ottenere il controller di visualizzazione post superiore in questo modo

 let viewController = UIApplication.topMostViewController 

Una cosa da notare è che se c’è un UIAlertController attualmente visualizzato, UIApplication.topMostViewController restituirà un UIAlertController .

Versione di Swift 2.0 della risposta di jungledev

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

Questo è il miglior modo ansible che ho provato. Se dovrebbe aiutare qualcuno …

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

Controlla sempre la configurazione della build se stai eseguendo la tua app con debug o release.

NOTA IMPORTANTE: non è ansible testarlo senza eseguire l’app in modalità di debug

Questa era la mia soluzione

Ho creato una categoria per l’applicazione UIApplication con visibleViewControllers proprietà visibleViewControllers . L’idea principale è piuttosto semplice. Ho ingrandito la viewDidAppear e viewDidDisappear metodi in UIViewController . Nel metodo viewDidAppear viewController viene aggiunto allo stack. Nel metodo viewDidDisappear viewController viene rimosso dallo stack. NSPointerArray viene utilizzato al posto di NSArray per archiviare i riferimenti deboli di UIViewController . Questo approccio funziona per qualsiasi gerarchia di viewControllers.

UIApplication + VisibleViewControllers.h

 #import  @interface UIApplication (VisibleViewControllers) @property (nonatomic, readonly) NSArray<__kindof UIViewController *> *visibleViewControllers; @end 

UIApplication + VisibleViewControllers.m

 #import "UIApplication+VisibleViewControllers.h" #import  @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

Versione 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...fromOpaque(pointer).takeUnretainedValue() as? UIViewController if viewController.isEqual(self) { pointers.removePointer(at: i) break } } } uiapplication_visibleviewcontrollers_viewDidDisappear(animated) } } 

https://gist.github.com/medvedzzz/ee6f4071639d987793977dba04e11399