Rufen Sie den aktuell angezeigten UIViewController in AppDelegate.m auf dem Bildschirm ab

126

Der aktuelle UIViewControllerBildschirm muss auf Push-Benachrichtigungen von APNs reagieren, indem einige Ausweisansichten festgelegt werden. Aber wie könnte ich die UIViewControllerIn-Methode bekommen application:didReceiveRemoteNotification: von AppDelegate.m?

Ich habe versucht self.window.rootViewController, die aktuelle Anzeige zu erhalten UIViewController, es kann sich um eine UINavigationViewControlleroder eine andere Art von Ansichtssteuerung handeln. Und ich finde heraus, dass die visibleViewControllerEigenschaft von UINavigationViewControllerverwendet werden kann, um das UIViewControllerauf den Bildschirm zu bekommen . Aber was könnte ich tun, wenn es kein ist UINavigationViewController?

Jede Hilfe wird geschätzt! Der zugehörige Code lautet wie folgt.

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

}
lu Yuan
quelle

Antworten:

99

Sie können das rootViewControllerauch verwenden, wenn Ihr Controller kein UINavigationController:

UIViewController *vc = self.window.rootViewController;

Sobald Sie den Root-View-Controller kennen, hängt dies davon ab, wie Sie Ihre Benutzeroberfläche erstellt haben. Möglicherweise finden Sie jedoch eine Möglichkeit, durch die Controller-Hierarchie zu navigieren.

Wenn Sie weitere Details zur Definition Ihrer App angeben, gebe ich möglicherweise weitere Hinweise.

BEARBEITEN:

Wenn Sie die oberste Ansicht (nicht View Controller) möchten , können Sie dies überprüfen

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

obwohl diese Ansicht möglicherweise unsichtbar ist oder sogar von einigen ihrer Unteransichten abgedeckt wird ...

Auch dies hängt von Ihrer Benutzeroberfläche ab, aber dies könnte helfen ...

Sergio
quelle
19
Das Problem dabei ist, wenn die sichtbare Ansicht nicht zum Root-View-Controller gehört (bei modalen Ansichten und dergleichen).
Dima
Ja, ich will. Aber es ist vielleicht ein UITabViewController. Gibt es nicht eine direkte Methode, um den UIViewController auf den Bildschirm zu bringen?
Lu Yuan
2
Sie sehen, UINavigationController bietet Ihnen eine Möglichkeit zu erkennen, welcher Controller der oberste ist. Ihr Root-Controller sollte auf irgendeine Weise die gleichen Informationen bereitstellen. Es kann im Allgemeinen nicht abgeleitet werden, da es streng davon abhängt, wie Sie Ihre Benutzeroberfläche erstellt haben, und es keine explizite Controller-Hierarchie gibt (wie dies bei Ansichten der Fall ist). Sie können Ihrem Root-Controller einfach eine Eigenschaft hinzufügen und ihren Wert festlegen, wenn Sie einen neuen Controller darüber "schieben".
Sergio
1
Solange der Wert auf dem neuesten Stand gehalten wird, scheint mir das auch ein guter Weg zu sein.
Dima
4
Es gibt keine direkte Möglichkeit, von einer UIViewInstanz zum Controller zu gelangen . rootViewControllerist nicht unbedingt der aktuell gezeigte Controller. Es befindet sich ganz oben in der Ansichtshierarchie.
Gingi
101

Ich liebe immer Lösungen, die Kategorien beinhalten, da sie fest miteinander verbunden sind und leicht wiederverwendet werden können.

Also habe ich eine Kategorie auf UIWindow erstellt. Sie können jetzt visibleViewController unter UIWindow aufrufen. Dadurch erhalten Sie den Controller für sichtbare Ansichten, indem Sie die Controller-Hierarchie durchsuchen. Dies funktioniert, wenn Sie einen Navigations- und / oder Registerkarten-Controller verwenden. Wenn Sie einen anderen Controllertyp vorschlagen möchten, lassen Sie es mich bitte wissen und ich kann ihn hinzufügen.

UIWindow + PazLabs.h (Header-Datei)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (Implementierungsdatei)

#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

Schnelle Version

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
            }
        }
    }
}
zirinisp
quelle
2
Wie kann ich dies für eine schnelle Version verwenden?
Vijay Singh Rana
2
Ich kann deine Frage nicht verstehen. Kopieren Sie Ihren Code und fügen Sie ihn ein.
Zirinisp
Was ist mit benutzerdefinierten Container VC?
Mingming
@Mingming Es sollte nicht so schwierig sein, ein zusätzliches hinzuzufügen, wenn überprüft werden soll, ob es sich um den benutzerdefinierten Container VC handelt (in der Methode getVisibielController), und wenn ja, den "sichtbaren" Controller zurückgibt, der für die meisten benutzerdefinierten normalerweise vc.childControllers.lastObject ist Container-VC-Implementierungen (nehme ich an), würden aber davon abhängen, wie sie implementiert werden.
Gadu
1
Ich habe gerade eine Antwort mit dem gleichen Ansatz wie in dieser Antwort gepostet, mit Ausnahme einer aktualisierten Syntax: Sie verwendet einen Switch-Case und folgt den Swift 3-Namenskonventionen: stackoverflow.com/a/42486823/3451975
Jeehut
43

Einfache Erweiterung für UIApplication in Swift (kümmert sich sogar um moreNavigationController UITabBarControllerauf dem 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
    }
}

Einfache Verwendung:

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

Funktioniert perfekt :-)

UPDATE für sauberen Code:

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
    }
}
Bartłomiej Semańczyk
quelle
1
Dies scheint Code für Swift 2.x zu sein. Swift 3.x hat kein "Wo" mehr. Außerdem ist "sharedApplication ()" jetzt "shared". Keine große Sache. Das Update dauert nur eine Minute. Könnte gut sein zu erwähnen, dass es Rekursion verwendet. Außerdem sollte jeder Aufruf von topViewController das Präfix "base:" benötigen.
Jeff Muir
37

Sie können auch eine Benachrichtigung über NSNotificationCenter veröffentlichen. Auf diese Weise können Sie eine Reihe von Situationen bewältigen, in denen das Durchlaufen der View-Controller-Hierarchie schwierig sein kann - beispielsweise wenn Modalitäten usw. präsentiert werden.

Z.B,

// 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 jedem Ihrer 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
   }
}

Sie können diesen Ansatz auch für Instrumentensteuerungen verwenden, die beim Empfang einer Benachrichtigung aktualisiert werden müssen und von mehreren Ansichtssteuerungen verwendet werden. Behandeln Sie in diesem Fall die Aufrufe zum Hinzufügen / Entfernen von Beobachtern in den Methoden init bzw. dealloc.

Aneil Mallavarapu
quelle
1
Was ist addObserver:bardrin viewDidLoad? Muss ich durch ersetzen self?
CainaSouza
Vielen Dank für den Hinweis - es sollte selbst sein. Ich werde die Antwort aktualisieren.
Aneil Mallavarapu
Absturz beim Abrufen aller Schlüssel von userInfo .. Irgendeine Idee? [NSConcreteNotification allKeys]: Nicht erkannter Selektor an Instanz 0x1fd87480 2013-07-05 16: 10: 36.469 Providence gesendet Selektor an Instanz 0x1fd87480 'gesendet
Awais Tariq
@AwaisTariq - Hmmm - Ich vermute, dass das von iOS an didReceiveRemoteNotification übergebene Objekt kein NSDictionary ist, wie die Schnittstelle angibt.
Aneil Mallavarapu
Was ist, wenn der Benutzer noch nicht zu Ihrer Beobachterklasse navigiert ist? : /
Halbano
15

Code

Hier ist ein Ansatz, der die großartige Switch-Case-Syntax in Swift 3/4/5 verwendet :

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

Die Grundidee ist die gleiche wie in der Antwort von zirinisp, es wird nur eine schnellere 3+ Syntax verwendet.


Verwendung

Sie möchten wahrscheinlich eine Datei mit dem Namen erstellen UIWindowExtension.swift. Stellen Sie sicher, dass die import UIKitAnweisung enthalten ist, und kopieren Sie nun den obigen Erweiterungscode .

Auf der Anrufseite kann es entweder ohne einen bestimmten View Controller verwendet werden :

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

Oder wenn Sie wissen, dass Ihr sichtbarer Ansichts-Controller von einem bestimmten Ansichts-Controller aus erreichbar ist :

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

Ich hoffe, es hilft!

Jeehut
quelle
Der dritte Fall stürzt aufgrund einer unendlichen Rekursion ab. Der Fix besteht darin, den vc als umzubenennen presentingViewControllerund presentingViewController.presentedViewControllerals Parameter an die rekursive Methode zu übergeben.
Ikhsan Assaat
Ich habe es nicht ganz verstanden, sorry. Du meinst UIWindow.visibleViewController(from: presentedViewController)sollte stattdessen sein UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut
Richtig, presentedViewControllerund viewControllerist das gleiche Objekt und es wird die Methode mit sich selbst aufrufen, bis der Stapel überläuft (Wortspiel beabsichtigt). So wird es sein case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat
1
Diese Lösung funktionierte, wenn andere dies nicht taten. Sie sollten auf Swift 5 aktualisieren. Im Wesentlichen keine Änderung. Aktualisieren Sie einfach den Header für Ihre Antwort.
TM Lynch
14

Ich habe festgestellt, dass iOS 8 alles vermasselt hat. In iOS 7 gibt es eine neue UITransitionViewAnsichtshierarchie, wenn Sie eine modale Darstellung haben UINavigationController. Wie auch immer, hier ist mein Code, der die oberste VC erhält. Das Anrufen getTopMostViewControllersollte eine VC zurückgeben, die Sie wie folgt senden können sollten presentViewController:animated:completion. Der Zweck ist es, Ihnen eine VC zu besorgen, mit der Sie eine modale VC präsentieren können, damit sie höchstwahrscheinlich bei Containerklassen wie UINavigationControllerund NICHT bei der darin enthaltenen VC anhält und zurückkehrt . Sollte nicht schwer sein, den Code auch dafür anzupassen. Ich habe diesen Code in verschiedenen Situationen in iOS 6, 7 und 8 getestet. Bitte lassen Sie mich wissen, wenn Sie Fehler finden.

+ (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;
}
nvrtd frst
quelle
Bitte duplizieren Sie keine Antworten - markieren Sie die Fragen entweder als Duplikate, wenn dies der Fall ist, oder beantworten Sie die einzelnen Fragen mit der spezifischen Antwort, die sie verdienen, wenn sie keine Duplikate sind.
Flexo
13

Weit weniger Code als alle anderen Lösungen:

Objective-C-Version:

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

    return topViewController;
}

Swift 2.0-Version: (Gutschrift geht an Steve.B)

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

Funktioniert überall in Ihrer App, auch mit Modalen.

Jungledev
quelle
1
Dies behandelt nicht die Situation, in der der dargestellte Ansichtscontroller einen UINavigationControllereigenen untergeordneten Controller hat.
Levigroker
@levigroker, vielleicht haben Sie Ihre Ansichten so aufgebaut? Es funktioniert gut für mich, dies mit einem Nav zu verwenden. (so benutze ich es)
Jungledev
@jungledev Ich bin sicher, Sie sind richtig. Eine Lösung, die in allen View-Controller-Konfigurationen funktioniert, ist jedoch erforderlich.
Levigroker
@levigroker es tut Arbeit in allen Standard - vc - Konfigurationen die App ich die Arbeit an hat eine wirklich komplexe Architektur, wird von mehr als 500k Nutzer, und das funktioniert überall in der App verwendet. Vielleicht sollten Sie eine Frage mit Codebeispielen stellen, in der Sie gefragt werden, warum dies aus Ihrer Sicht nicht funktioniert.
Jungledev
jungledev Ich bin froh, dass dieser Code für Sie funktioniert, aber es scheint keine vollständige Lösung zu sein. Die Antwort von @ zirinisp funktioniert in meiner Situation perfekt.
Levigroker
8

zirinisps Antwort 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;
            }
        }
    }
}

Verwendung:

 if let topController = window.visibleViewController() {
            println(topController)
        }
Bobj-C
quelle
Es ist as!und navigationController.visibleViewController!für Swift 2.0
LinusGeffarth
7

Geben Sie den Titel für jeden ViewController an und erhalten Sie den Titel des aktuellen ViewControllers mit dem unten angegebenen Code.

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

Dann überprüfen Sie es anhand Ihres Titels wie folgt

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}
Neel Kamal
quelle
Dfntly die beste Antwort, auch können Sie Ihren viewController mit benennen:self.title = myPhotoView
Resty
5

Meins ist besser! :) :)

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
        }
    }
}
Nicolas Manzini
quelle
4

Warum nicht einfach den Push-Benachrichtigungscode im App-Delegaten verarbeiten? Steht es in direktem Zusammenhang mit einer Ansicht?

Sie können überprüfen, ob die Ansicht eines UIViewControllers derzeit sichtbar ist, indem Sie überprüfen, ob die windowEigenschaft der Ansicht einen Wert hat. Sehen Sie hier mehr .

Dima
quelle
Ja, es bezieht sich auf eine Ansicht, da ich die Ausweisansicht zeigen muss. Lass mich den Link überprüfen. danke :)
lu yuan
4

Nur Ergänzung zu @zirinisp Antwort.

Erstellen Sie eine Datei, benennen Sie sie UIWindowExtension.swiftund fügen Sie das folgende Snippet ein:

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
}

Verwenden Sie es überall als:

if let topVC = getTopViewController() {

}

Danke an @zirinisp.

Ashok
quelle
3

In Bezug auf NSNotificationCenter Post oben (leider kann ich nicht herausfinden, wo ich einen Kommentar darunter posten kann ...)

Für den Fall, dass einige den Fehler - [NSConcreteNotification allKeys] erhalten haben. Ändere das:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

dazu:

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

Das hat bei mir funktioniert. Ich habe viele Ziele mit unterschiedlichen Controllern, sodass frühere Antworten nicht zu funktionieren schienen.

Zuerst möchten Sie dies in Ihrer AppDelegate-Klasse:

var window: UIWindow?

dann in Ihrer Funktion

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

Dies ist der bestmögliche Weg, den ich ausprobiert habe. Wenn es jemandem helfen sollte ...

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

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

    return topController;
}
Mayur Deshmukh
quelle
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
        }
    }
}

Mit diesem können Sie ganz einfach den Top-Post-View-Controller so bekommen

let viewController = UIApplication.topMostViewController

Zu beachten ist, dass, wenn derzeit ein UIAlertController angezeigt wird, a zurückgegeben UIApplication.topMostViewControllerwird UIAlertController.

NSExceptional
quelle
1

Schnelle 2.0-Version von Jungledevs Antwort

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

Ich habe eine Kategorie für UIApplicationmit visibleViewControllersEigenschaft erstellt. Die Hauptidee ist ziemlich einfach. Ich zischte viewDidAppearund viewDidDisappearMethoden in UIViewController. In der viewDidAppearMethode viewController wird dem Stapel hinzugefügt. In der viewDidDisappearMethode viewController wird vom Stapel entfernt. NSPointerArraywird verwendet, anstatt NSArrayschwach zu speichernUIViewController Referenzen . Dieser Ansatz funktioniert für jede viewController-Hierarchie.

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

Swift 3 Version

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

Evgeny Mikhaylov
quelle
1

Überprüfen Sie immer Ihre Build-Konfiguration, wenn Sie Ihre App mit Debug oder Release ausführen.

WICHTIGER HINWEIS: Sie können es nicht testen, ohne Ihre App im Debug-Modus auszuführen

Das war meine Lösung

Vikram Sinha
quelle