Pobierz bieżące wyświetlanie UIViewController na ekranie w AppDelegate.m


126

Prąd UIViewControllerna ekranie musi odpowiadać na powiadomienia push z APN, ustawiając niektóre widoki znaczków. Ale jak mogę uzyskać UIViewControllermetodę in application:didReceiveRemoteNotification: of AppDelegate.m?

Próbowałem użyć, self.window.rootViewControlleraby uzyskać bieżące wyświetlanie UIViewController, może to być UINavigationViewControllerkontroler widoku lub inny rodzaj. I dowiaduję się, że visibleViewControllerwłaściwość UINavigationViewControllermożna wykorzystać, aby uzyskać UIViewControllerobraz na ekranie. Ale co mogę zrobić, jeśli to nie jest UINavigationViewController?

Każda pomoc jest mile widziana! Powiązany kod jest następujący.

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

}

Odpowiedzi:


99

Możesz użyć rootViewControllerrównież wtedy, gdy Twój kontroler nie jest UINavigationController:

UIViewController *vc = self.window.rootViewController;

Kiedy już znasz główny kontroler widoku, zależy to od tego, jak zbudowałeś swój interfejs użytkownika, ale prawdopodobnie możesz znaleźć sposób na poruszanie się po hierarchii kontrolerów.

Jeśli podasz więcej szczegółów na temat sposobu definiowania aplikacji, mogę podać więcej wskazówek.

EDYTOWAĆ:

Jeśli chcesz mieć najwyższy widok (nie kontroler widoku), możesz sprawdzić

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

chociaż ten widok może być niewidoczny lub nawet zakryty przez niektóre jego podglądy ...

znowu zależy to od interfejsu użytkownika, ale może to pomóc ...


19
Problem z tym polega na tym, że widoczny widok nie należy do głównego kontrolera widoku (w przypadku widoków modalnych itp.).
Dima

Tak. Ale może to UITabViewController. Czy nie ma bezpośredniej metody uzyskania UIViewController na ekranie?
lu yuan

2
widzisz, UINavigationController umożliwia sprawdzenie, który kontroler jest najwyższy; Twój główny kontroler powinien w jakiś sposób dostarczyć te same informacje. Nie można tego wywnioskować ogólnie, ponieważ zależy to ściśle od tego, jak zbudowałeś swój interfejs użytkownika i nie ma jawnej hierarchii kontrolerów (jak to się dzieje w przypadku widoków). Możesz po prostu dodać właściwość do swojego głównego kontrolera i ustawić jej wartość za każdym razem, gdy „wpychasz” nowy kontroler na górę.
Sergio

1
Dopóki wartość jest aktualizowana, wydaje mi się, że to dobry sposób.
Dima

4
Nie ma bezpośredniego sposobu na dotarcie do kontrolera z UIViewinstancji. rootViewControllerto nie koniecznie aktualnie pokazany sterownik. Znajduje się na samym szczycie hierarchii widoków.
Gingi

101

Zawsze uwielbiam rozwiązania obejmujące kategorie, ponieważ są one przykręcone i można je łatwo wykorzystać ponownie.

Więc stworzyłem kategorię w UIWindow. Możesz teraz wywołać visibleViewController w UIWindow, a to zapewni ci widoczny kontroler widoku, przeszukując hierarchię kontrolerów. Działa to, jeśli używasz nawigacji i / lub kontrolera paska kart. Jeśli chcesz zasugerować inny typ kontrolera, daj mi znać, a mogę go dodać.

UIWindow + PazLabs.h (plik nagłówkowy)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (plik implementacyjny)

#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

Szybka wersja

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
jak mogę tego użyć do szybkiej wersji?
Vijay Singh Rana

2
Nie rozumiem twojego pytania. Skopiuj i wklej w swoim kodzie.
zirinisp

A co z niestandardowym VC kontenera?
Mingming

@Mingming nie powinno być tak trudno dodać dodatkowy, jeśli chcesz sprawdzić, czy jest to niestandardowy VC kontenera (w metodzie getVisibielController), a jeśli tak, zwróć kontroler „widoczny”, którym zwykle byłby vc.childControllers.lastObject dla większości niestandardowych kontenerowe implementacje VC (przypuszczam), ale będzie zależeć od tego, jak jest zaimplementowany.
gadu

1
Właśnie opublikowałem odpowiedź z takim samym podejściem jak w tej odpowiedzi, z wyjątkiem zaktualizowanej składni: używa przełącznika i jest zgodna z konwencjami nazewnictwa Swift 3: stackoverflow.com/a/42486823/3451975
Jeehut

43

Proste rozszerzenie dla UIApplication w Swift (dba nawet o więcej NavigationController w 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
    }
}

Proste użycie:

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

Działa idealnie :-)

UPDATE dla czystego kodu:

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
Wygląda na to, że jest to kod dla Swift 2.x. Swift 3.x nie ma już „gdzie”. Ponadto „sharedApplication ()” jest teraz „udostępniane”. Nic takiego. Aktualizacja zajmie tylko chwilę. Warto wspomnieć, że używa rekurencji. Ponadto każde wywołanie topViewController powinno wymagać przedrostka „base:”.
Jeff Muir

37

Możesz również wysłać powiadomienie za pośrednictwem NSNotificationCenter. Pozwala to poradzić sobie z wieloma sytuacjami, w których przechodzenie przez hierarchię kontrolera widoku może być trudne - na przykład podczas prezentowania modali itp.

Na przykład,

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

W każdym z kontrolerów widoku:

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

Można również zastosować to podejście do elementów sterujących przyrządu, które wymagają aktualizacji po odebraniu powiadomienia i są używane przez kilka kontrolerów widoku. W takim przypadku obsłuż wywołania obserwatora add / remove w metodach init i dealloc, odpowiednio.


1
Co jest w addObserver:barśrodku viewDidLoad? Czy muszę zastąpić self?
CainaSouza

Dzięki za zwrócenie uwagi - to powinno być ja. Zaktualizuję odpowiedź.
Aneil Mallavarapu

awaria podczas pobierania wszystkich kluczy z informacji o użytkowniku .. Masz pomysł? [NSConcreteNotification allKeys]: nierozpoznany selektor wysłany do instancji 0x1fd87480 2013-07-05 16: 10: 36.469 Providence [2961: 907] *** Zakończenie aplikacji z powodu nieprzechwyconego wyjątku „NSInvalidArgumentException”, przyczyna: „- [NSConcreteNotification allKeysed]: nierozpoznane]: selektor wysłany do instancji 0x1fd87480 '
Awais Tariq

@AwaisTariq - Hmmm - przypuszczam, że obiekt przekazany przez iOS do didReceiveRemoteNotification nie jest w rzeczywistości NSDictionary, jak określa interfejs.
Aneil Mallavarapu

Co się stanie, jeśli użytkownik nie przeszedł jeszcze do Twojej klasy obserwatorów? : /
halbano

15

Kod

Oto podejście wykorzystujące świetną składnię przełączników w języku 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
        }
    }
}

Podstawowy pomysł jest taki sam jak w odpowiedzi zirinisp, po prostu używa składni bardziej podobnej do języka Swift 3+.


Stosowanie

Prawdopodobnie chcesz utworzyć plik o nazwie UIWindowExtension.swift. Upewnij się, że zawiera import UIKitinstrukcję, teraz skopiuj powyższy kod rozszerzenia .

Po stronie wywołania może być używany bez określonego kontrolera widoku :

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

Lub jeśli wiesz, że kontroler widocznego widoku jest dostępny z określonego kontrolera widoku :

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

Mam nadzieję, że to pomoże!


Trzeci przypadek ulegnie awarii z powodu nieskończonej rekurencji. Rozwiązaniem jest zmiana nazwy vc na presentingViewControlleri przekazanie presentingViewController.presentedViewControllerjako parametru do metody rekurencyjnej.
Ikhsan Assaat

Przepraszam, nie całkiem to rozumiem. Masz na myśli UIWindow.visibleViewController(from: presentedViewController)zamiast tego UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut

poprawne presentedViewControlleri viewControllerjest tym samym obiektem i będzie wywoływać metodę ze sobą, dopóki stos się nie przepełni (gra słów zamierzona). Tak będzie case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat

1
To rozwiązanie działało, gdy inni nie. Należy zaktualizować do Swift 5. W zasadzie bez zmian. Po prostu zaktualizuj nagłówek swojej odpowiedzi.
TM Lynch

14

Odkryłem, że iOS 8 wszystko schrzanił. W iOS 7 pojawia się nowa UITransitionViewhierarchia widoków, ilekroć masz prezentację modalną UINavigationController. W każdym razie, oto mój kod, który wyszukuje najwyższy VC. Połączenie getTopMostViewControllerpowinno zwrócić VC, który powinieneś być w stanie wysłać wiadomość, taką jak presentViewController:animated:completion. Jego celem jest uzyskanie VC, którego możesz użyć do zaprezentowania modalnego VC, więc najprawdopodobniej zatrzyma się i wróci do klas kontenerów, takich jak, UINavigationControllera NIE VC w nich zawartego. Dostosowanie kodu również do tego nie powinno być trudne. Przetestowałem ten kod w różnych sytuacjach w iOS 6, 7 i 8. Daj mi znać, jeśli znajdziesz błędy.

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

Nie duplikuj odpowiedzi - oznacz pytania jako duplikaty, jeśli tak jest, lub odpowiedz na poszczególne pytania konkretną odpowiedzią, na którą zasługują, jeśli nie są one duplikatami.
Flexo

13

O wiele mniej kodu niż wszystkie inne rozwiązania:

Wersja Objective-C:

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

    return topViewController;
}

Wersja Swift 2.0: (autorstwo dla Steve.B)

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

Działa w dowolnym miejscu aplikacji, nawet z modami.


1
Nie rozwiązuje to sytuacji, w której prezentowany kontroler widoku jest kontrolerem, UINavigationControllerktóry ma własne elementy podrzędne.
levigroker

@levigroker, może to sposób, w jaki zaprojektowałeś swoje poglądy? W przypadku mnie działa to dobrze z nawigacją. (tak go używam)
jungledev

@jungledev Jestem pewien, że masz rację. To powiedziawszy, potrzebne jest rozwiązanie, które działa we wszystkich konfiguracjach kontrolera widoku.
levigroker

@levigroker to robi praca we wszystkich standardowych vc Konfiguracje- app pracuję nad ma bardzo złożoną strukturę, jest używany przez ponad 500k użytkowników, a wszędzie to działa w aplikacji. Może powinieneś zadać pytanie, dlaczego to nie działa w Twoim widoku, wraz z przykładami kodu?
jungledev

jungledev Cieszę się, że ten kod działa dla Ciebie, ale nie wydaje się być kompletnym rozwiązaniem. Odpowiedź @ zirinisp działa doskonale w mojej sytuacji.
levigroker

8

Odpowiedź zirinisp w 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;
            }
        }
    }
}

Stosowanie:

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

To as!i navigationController.visibleViewController!dla Swift 2.0
LinusGeffarth

7

Określ tytuł dla każdego ViewController, a następnie uzyskaj tytuł bieżącego ViewController za pomocą kodu podanego poniżej.

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

Następnie sprawdź to po tytule w ten sposób

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

Dfntly najlepsza odpowiedź, możesz również nazwać swój viewController za pomocą:self.title = myPhotoView
Resty

5

Mój jest lepszy! :)

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

Dlaczego nie obsłużyć po prostu kodu powiadomienia wypychanego w delegacie aplikacji? Czy jest to bezpośrednio związane z widokiem?

Możesz sprawdzić, czy widok UIViewControllera jest obecnie widoczny, sprawdzając, czy jego windowwłaściwość widoku ma wartość. Zobacz więcej tutaj .


Tak, jest to związane z widokiem, ponieważ muszę pokazać widok odznaki. pozwól mi sprawdzić link. dziękuję :)
lu yuan

4

Tylko dodatek do odpowiedzi @zirinisp.

Utwórz plik, nazwij go UIWindowExtension.swifti wklej następujący fragment:

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
}

Używaj go wszędzie jako:

if let topVC = getTopViewController() {

}

Dzięki @zirinisp.


3

Odnośnie postu NSNotificationCenter powyżej (przepraszam, nie mogę dowiedzieć się, gdzie zamieścić komentarz pod nim ...)

Na wypadek, gdyby niektórzy otrzymywali pewnego rodzaju błąd - [NSConcreteNotification allKeys]. Zmień to:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

do tego:

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

3

To zadziałało dla mnie. Mam wiele celów, które mają różne kontrolery, więc poprzednie odpowiedzi wydawały się nie działać.

najpierw chcesz to w swojej klasie AppDelegate:

var window: UIWindow?

następnie w swojej funkcji

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

2

To najlepszy możliwy sposób, jaki wypróbowałem. Gdyby to komukolwiek pomogło ...

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

Dzięki temu możesz łatwo uzyskać kontroler widoku z góry w taki sposób

let viewController = UIApplication.topMostViewController

Należy zauważyć, że jeśli obecnie wyświetlany jest kontroler UIAlertController, UIApplication.topMostViewControllerzwróci plik UIAlertController.


1

Odpowiedź dżungliediewa w wersji Swift 2.0

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

1

Utworzyłem kategorię dla UIApplicationz visibleViewControllerswłasnością. Główny pomysł jest dość prosty. I swizzled viewDidAppeari viewDidDisappearmetody UIViewController. W viewDidAppearmetodzie viewController jest dodawany do stosu. W viewDidDisappearmetodzie viewController jest usuwany ze stosu. NSPointerArrayjest używany zamiast NSArraydo przechowywania UIViewControllerodniesień do słabych . To podejście działa dla dowolnej hierarchii 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

Wersja 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

Zawsze sprawdzaj konfigurację kompilacji, jeśli używasz aplikacji z debugowaniem lub wydaniem.

WAŻNA UWAGA: nie możesz go przetestować bez uruchamiania aplikacji w trybie debugowania

To było moje rozwiązanie

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.