Ottieni UIViewController attualmente visualizzato sullo schermo in AppDelegate.m


126

La corrente UIViewControllersullo schermo deve rispondere alle notifiche push dagli APN, impostando alcune viste badge. Ma come posso ottenere il UIViewControllermetodo in application:didReceiveRemoteNotification: of AppDelegate.m?

Ho provato a usare self.window.rootViewControllerper ottenere la visualizzazione corrente UIViewController, potrebbe essere un UINavigationViewControllero qualche altro tipo di controller di visualizzazione. E scopro che la visibleViewControllerproprietà di UINavigationViewControllerpuò essere usata per ottenere UIViewControllersullo schermo. Ma cosa potrei fare se non fosse un UINavigationViewController?

Qualsiasi aiuto è apprezzato! Il codice correlato è 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

}

Risposte:


99

Puoi usare rootViewControlleranche quando il tuo controller non è un UINavigationController:

UIViewController *vc = self.window.rootViewController;

Una volta che conosci il controller della vista principale, dipende da come hai creato l'interfaccia utente, ma puoi probabilmente trovare un modo per navigare attraverso la gerarchia dei controller.

Se fornisci ulteriori dettagli sul modo in cui hai definito l'app, allora potrei dare qualche suggerimento in più.

MODIFICARE:

Se si desidera la vista più in alto (non visualizzare il controller), è possibile verificare

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

anche se questa vista potrebbe essere invisibile o addirittura coperta da alcune delle sue visualizzazioni secondarie ...

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


19
Il problema è che la vista visibile non appartiene al controller della vista radice (nel caso di viste modali e simili).
Dima,

Sì, certamente. Ma forse è un UITabViewController. Non esiste un metodo diretto per visualizzare UIViewController sullo schermo?
lu yuan,

2
bene, vedete, UINavigationController fornisce un modo per sapere quale controller è il più alto; il tuo controller di root dovrebbe fornire le stesse informazioni in qualche modo. Non può essere dedotto in generale perché dipende strettamente da come hai creato la tua UI e non esiste una gerarchia di controller esplicita (come accade per le viste). È possibile semplicemente aggiungere una proprietà al controller di root e impostarne il valore ogni volta che si "spinge" un nuovo controller in cima.
sergio,

1
Finché il valore è aggiornato, quello sembra un buon modo di andare anche a me.
Dima,

4
Non esiste un modo diretto per accedere al controller da UIViewun'istanza. nonrootViewController è necessariamente il controller attualmente mostrato. È solo nella parte superiore della gerarchia della vista.
Gingi,

101

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

Quindi ho creato una categoria su UIWindow. Ora puoi chiamare visibleViewController su UIWindow e questo ti porterà il controller di visualizzazione visibile cercando nella gerarchia del controller. Funziona se si utilizza il controller di navigazione e / o della barra delle schede. Se hai un altro tipo di controller da suggerire, fammelo sapere e posso aggiungerlo.

UIWindow + PazLabs.h (file di intestazione)

#import <UIKit/UIKit.h>

@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 rapida

public extension UIWindow {
    public var visibleViewController: UIViewController? {
        return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
    }

    public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
        if let nc = vc as? UINavigationController {
            return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
        } else if let tc = vc as? UITabBarController {
            return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
        } else {
            if let pvc = vc?.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(pvc)
            } else {
                return vc
            }
        }
    }
}

2
come posso usarlo per la versione rapida?
Vijay Singh Rana,

2
Non riesco a capire la tua domanda. Copia e incolla il codice.
Zirinisp,

Che dire del contenitore personalizzato VC?
Mingming,

@Mingming non dovrebbe essere così difficile aggiungere un extra se verificare se si tratta del contenitore personalizzato VC (nel metodo getVisibielController) e in tal caso restituire il controller "visibile", che in genere sarebbe vc.childControllers.lastObject per la maggior parte personalizzato implementazioni VC container (suppongo), ma dipenderebbero da come sono state implementate.
gadu,

1
Ho appena pubblicato una risposta con lo stesso approccio in questa risposta ad eccezione di una sintassi aggiornamento: Si sta usando un interruttore caso e segue le convenzioni di denominazione Swift 3: stackoverflow.com/a/42486823/3451975
Jeehut

43

Semplice estensione per UIApplication in Swift (si preoccupa ancora di piùNavigationController all'interno di UITabBarControlleriPhone) :

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

Semplice utilizzo:

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

Funziona perfettamente :-)

AGGIORNAMENTO per codice pulito:

extension UIViewController {
    var top: UIViewController? {
        if let controller = self as? UINavigationController {
            return controller.topViewController?.top
        }
        if let controller = self as? UISplitViewController {
            return controller.viewControllers.last?.top
        }
        if let controller = self as? UITabBarController {
            return controller.selectedViewController?.top
        }
        if let controller = presentedViewController {
            return controller.top
        }
        return self
    }
}

1
Questo sembra essere il codice per Swift 2.x. Swift 3.x non ha più "dove". Inoltre, "sharedApplication ()" è ora "condiviso". Non un grande affare. Ci vuole solo un minuto per l'aggiornamento. Potrebbe essere utile ricordare che utilizza la ricorsione. Inoltre, ogni chiamata a topViewController dovrebbe richiedere il prefisso "base:".
Jeff Muir,

37

È inoltre possibile pubblicare una notifica tramite NSNotificationCenter. Questo ti consente 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 controller di visualizzazione:

-(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 possibile utilizzare questo approccio per i controlli dello strumento che devono essere aggiornati quando viene ricevuta una notifica e vengono utilizzati da più controller di visualizzazione. In tal caso, gestire le chiamate osservatore aggiungi / rimuovi nei metodi init e dealloc, rispettivamente.


1
Cosa c'è addObserver:bardentro viewDidLoad? Devo sostituire con self?
CainaSouza,

Grazie per averlo sottolineato - dovrebbe essere autonomo. Aggiornerò la risposta.
Aneil Mallavarapu,

arresto anomalo mentre si ottengono tutte le chiavi dall'utenteInfo .. Qualche idea? [NSConcreteNotification allKeys]: selettore non riconosciuto inviato all'istanza 0x1fd87480 2013-07-05 16: 10: 36.469 Providence [2961: 907] *** Terminazione dell'app a causa dell'eccezione non rilevata 'NSInvalidArgumentException', motivo: '- [NSConcreteNotification allKeys]: non riconosciuto selettore inviato all'istanza 0x1fd87480 '
Awais Tariq il

@AwaisTariq - Hmmm - la mia ipotesi è che l'oggetto passato da iOS a didReceiveRemoteNotification non sia in realtà un NSDictionary, come specifica l'interfaccia.
Aneil Mallavarapu,

Cosa succede se l'utente non è ancora arrivato alla tua classe di osservatori? : /
halbano il

15

Codice

Ecco un approccio che utilizza l'eccezionale sintassi switch-case in 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'idea di base è la stessa della risposta di zirinisp, sta usando solo una sintassi più simile a Swift 3+.


uso

Probabilmente vuoi creare un file chiamato UIWindowExtension.swift. Assicurati che includa la import UIKitdichiarazione, ora copia il codice di estensione sopra .

Sul lato chiamata può essere utilizzato senza alcun controller di visualizzazione specifico :

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

Oppure se sai che il controller della vista visibile è raggiungibile da un controller della vista specifico :

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

Spero possa essere d'aiuto!


Il terzo caso andrà in crash a causa di una ricorsione infinita. La correzione consiste nel rinominare vc as presentingViewControllere passare presentingViewController.presentedViewControllercome parametro al metodo ricorsivo.
Ikhsan Assaat,

Non ho capito bene, scusa. Vuoi dire che UIWindow.visibleViewController(from: presentedViewController)dovrebbe essere invece UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut,

corretto, presentedViewControllered viewControllerè lo stesso oggetto e chiamerà il metodo con se stesso fino a quando lo stack trabocca (gioco di parole previsto). Così sarà case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat

1
Questa soluzione ha funzionato quando altri no. È necessario aggiornare a Swift 5. Sostanzialmente nessuna modifica. Aggiorna semplicemente l'intestazione per la tua risposta.
TM Lynch,

14

Ho scoperto che iOS 8 ha rovinato tutto. In iOS 7 è presente una nuova UITransitionViewgerarchia di visualizzazione ogni volta che si presenta in modo modale UINavigationController. Comunque, ecco il mio codice che trova ottiene il VC più in alto. La chiamata getTopMostViewControllerdovrebbe restituire un VC che dovrebbe essere in grado di inviare un messaggio simile presentViewController:animated:completion. Lo scopo è quello di procurarti un VC che puoi utilizzare per presentare un VC modale, quindi molto probabilmente si fermerà e tornerà a classi di container come UINavigationControllere NON al VC contenuto al loro interno. Non dovrebbe essere difficile adattare il codice per farlo. 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;
}

Si prega di non duplicare le risposte: contrassegnare le domande come duplicate se lo sono o rispondere alle singole domande con la risposta specifica che meritano se non sono duplicati.
Flexo

13

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


1
Ciò non gestisce la situazione in cui il controller di visualizzazione presentato è un UINavigationControlleroggetto con i propri figli.
levigroker,

@levigroker, forse è il modo in cui hai progettato le tue opinioni? Per me funziona bene con questo Nav. (è così che lo sto usando)
jungledev il

@jungledev Sono sicuro che hai ragione. Detto questo, è necessaria una soluzione che funzioni in tutte le configurazioni del controller di visualizzazione.
levigroker,

@levigroker si fa il lavoro in tutte le vc serie Configurazioni- I lavori app ha un'architettura molto complesso, viene utilizzato da oltre 500k utenti, e questo funziona ovunque in app. Forse dovresti pubblicare una domanda che ti chiede perché non funziona secondo te, con esempi di codice?
jungledev,

jungledev Sono felice che questo codice funzioni per te, ma non sembra essere una soluzione completa. La risposta di @ zirinisp funziona perfettamente nella mia situazione.
levigroker,

8

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

È as!e navigationController.visibleViewController!per Swift 2.0
LinusGeffarth l'

7

Specificare il titolo per ciascun ViewController e quindi ottenere il titolo del ViewController corrente dal codice indicato di seguito.

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

Quindi controlla il tuo titolo in questo modo

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

Sicuramente la risposta migliore, anche tu puoi nominare il tuo viewController con:self.title = myPhotoView
Resty

5

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

4

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

Puoi verificare se la vista di un UIViewController è attualmente visibile controllando se la windowproprietà della vista ha un valore. Vedi di più qui .


Sì, è correlato a una vista, poiché devo mostrare la vista badge. fammi controllare il link. grazie :)
lu yuan

4

Solo aggiunta alla risposta @zirinisp.

Crea un file, chiamalo UIWindowExtension.swifte incolla il seguente frammento:

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.


3

Per quanto riguarda NSNotificationCenter Post sopra (spiacenti, non riesco a scoprire dove pubblicare un commento sotto di esso ...)

Nel caso in cui alcuni ottengano l'errore - [NSConcreteNotification allKeys] di sorta. Cambia questo:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

a questa:

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

3

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

per prima cosa lo vuoi nella tua classe 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!")    
   }
}

2

Questo è il modo migliore che ho provato. Se dovrebbe aiutare qualcuno ...

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

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

    return topController;
}

2
extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
    }
}

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else {
            return self
        }
    }
}

Con questo puoi facilmente ottenere il controller di visualizzazione post superiore in questo modo

let viewController = UIApplication.topMostViewController

Una cosa da notare è che se è attualmente visualizzato un UIAlertController, UIApplication.topMostViewControllerverrà restituito a UIAlertController.


1

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

1

Ho creato una categoria per UIApplicationcon visibleViewControllersproprietà. L'idea principale è piuttosto semplice. Ho sfrigolato viewDidAppeare ho usato i viewDidDisappearmetodi UIViewController. Nel viewDidAppearmetodo viewController viene aggiunto allo stack. Nel viewDidDisappearmetodo viewController viene rimosso dallo stack. NSPointerArrayè usato al posto di NSArraymemorizzare UIViewControlleri riferimenti deboli . Questo approccio funziona per qualsiasi gerarchia di viewController.

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

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

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


1

Controlla sempre la configurazione della build se stai eseguendo l'app con debug o rilascio.

NOTA IMPORTANTE: non puoi essere in grado di testarlo senza eseguire l'app in modalità debug

Questa era la mia soluzione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.