Dapatkan UIViewController yang sedang ditampilkan di layar di AppDelegate.m

126

Arus UIViewControllerdi layar perlu merespons pemberitahuan push dari APN, dengan menetapkan beberapa tampilan lencana. Tetapi bagaimana saya bisa mendapatkan UIViewControllermetode in application:didReceiveRemoteNotification: of AppDelegate.m?

Saya mencoba menggunakan self.window.rootViewControlleruntuk mendapatkan tampilan saat ini UIViewController, mungkin berupa UINavigationViewControlleratau beberapa jenis pengontrol tampilan lainnya. Dan saya mengetahui bahwa visibleViewControllerproperti dari UINavigationViewControllerdapat digunakan untuk mendapatkan UIViewControllerdi layar. Tapi apa yang bisa saya lakukan jika itu bukan UINavigationViewController?

Bantuan apa pun dihargai! Kode terkait adalah sebagai berikut.

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
sumber

Jawaban:

99

Anda dapat menggunakan rootViewControllerjuga saat pengontrol Anda bukan UINavigationController:

UIViewController *vc = self.window.rootViewController;

Setelah Anda mengetahui pengontrol tampilan root, maka itu tergantung pada bagaimana Anda telah membangun UI Anda, tetapi Anda mungkin dapat menemukan cara untuk menavigasi melalui hierarki pengendali.

Jika Anda memberikan detail lebih lanjut tentang cara Anda mendefinisikan aplikasi, maka saya mungkin memberikan beberapa petunjuk lagi.

EDIT:

Jika Anda ingin paling atas tampilan (bukan tampilan controller), Anda bisa memeriksa

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

meskipun tampilan ini mungkin tidak terlihat atau bahkan ditutupi oleh beberapa subview-nya ...

lagi, itu tergantung pada UI Anda, tetapi ini mungkin membantu ...

sergio
sumber
19
Masalah dengan ini adalah jika tampilan yang terlihat bukan milik pengontrol tampilan root (dalam kasus tampilan modal dan semacamnya).
Dima
Ya saya lakukan. Tapi mungkin itu UITabViewController. Apakah tidak ada metode langsung untuk mendapatkan UIViewController di layar?
lu yuan
2
baik, Anda tahu, UINavigationController menyediakan cara bagi Anda untuk mengetahui controller mana yang paling atas; root controller Anda harus memberikan info yang sama. Itu tidak dapat disimpulkan secara umum karena sangat bergantung pada bagaimana Anda membangun UI Anda dan tidak ada hierarki pengendali eksplisit (seperti yang terjadi untuk tampilan). Anda cukup menambahkan properti ke pengendali root Anda dan menetapkan nilainya setiap kali Anda "mendorong" pengendali baru di atasnya.
sergio
1
Selama nilainya tetap up to date, itu sepertinya cara yang baik untuk pergi ke saya juga.
Dima
4
Tidak ada cara langsung untuk sampai ke controller dari UIViewinstance. rootViewControlleradalah tidak tentu controller saat ditampilkan. Itu hanya di bagian atas hierarki tampilan.
Gingi
101

Saya selalu suka solusi yang melibatkan kategori karena mereka baut dan dapat dengan mudah digunakan kembali.

Jadi saya membuat kategori di UIWindow. Anda sekarang dapat memanggil visibleViewController di UIWindow dan ini akan memberi Anda pengontrol tampilan yang terlihat dengan menelusuri hierarki pengontrol. Ini berfungsi jika Anda menggunakan navigasi dan / atau pengontrol tab bar. Jika Anda memiliki tipe pengontrol lain yang disarankan, beri tahu saya dan saya dapat menambahkannya.

UIWindow + PazLabs.h (file header)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (file implementasi)

#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

Versi Swift

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

    public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
        if let nc = vc as? UINavigationController {
            return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
        } else if let tc = vc as? UITabBarController {
            return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
        } else {
            if let pvc = vc?.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(pvc)
            } else {
                return vc
            }
        }
    }
}
zirinisp
sumber
2
bagaimana saya bisa menggunakan ini untuk versi cepat?
Vijay Singh Rana
2
Saya tidak bisa mengerti pertanyaan Anda. Salin dan tempel di dalam kode Anda.
zirinisp
Bagaimana dengan VC kontainer kustom?
Mingming
@Mingming seharusnya tidak terlalu sulit untuk menambahkan ekstra jika untuk memeriksa apakah itu wadah kustom VC (dalam metode getVisibielController) dan jika demikian kembalikan pengontrol "terlihat", yang biasanya akan menjadi vc.childControllers.lastObject untuk sebagian besar kustom implementasi kontainer VC (saya kira), tetapi akan tergantung pada bagaimana implementasinya.
gadu
1
Saya baru saja memposting jawaban dengan pendekatan yang sama seperti dalam jawaban ini kecuali untuk sintaks yang diperbarui: Ini menggunakan switch-case dan mengikuti konvensi penamaan Swift 3: stackoverflow.com/a/42486823/3451975
Jeehut
43

Ekstensi sederhana untuk aplikasi UIAUITabBarController di Swift (peduli bahkan lebih banyak tentang Navigasi Navigasi di dalam 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
    }
}

Penggunaan sederhana:

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

Bekerja sempurna :-)

UPDATE untuk kode bersih:

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
sumber
1
Tampaknya ini adalah kode untuk Swift 2.x. Swift 3.x tidak memiliki "di mana" lagi. Juga, "sharedApplication ()" sekarang "dibagikan". Bukan masalah besar. Hanya perlu satu menit untuk memperbarui. Mungkin baik untuk menyebutkan bahwa ia menggunakan rekursi. Selain itu, setiap panggilan ke topViewController harus memerlukan awalan "base:".
Jeff Muir
37

Anda juga dapat memposting pemberitahuan melalui NSNotificationCenter. Ini membuat Anda berurusan dengan sejumlah situasi di mana melintasi hierarki pengontrol tampilan mungkin rumit - misalnya ketika modem disajikan, dll.

Misalnya,

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

Di setiap Pengontrol Tampilan Anda:

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

Anda juga dapat menggunakan pendekatan ini untuk kontrol instrumen yang perlu diperbarui ketika pemberitahuan diterima dan digunakan oleh beberapa pengontrol tampilan. Dalam hal ini, masing-masing menangani panggilan add / remove observer di init dan dealloc.

Aneil Mallavarapu
sumber
1
Apa yang ada addObserver:bardi dalam viewDidLoad? Apakah saya harus mengganti dengan self?
CainaSouza
Terima kasih telah menunjukkan itu - itu harus diri sendiri. Saya akan memperbarui jawabannya.
Aneil Mallavarapu
lumpuh saat mendapatkan semua kunci dari userInfo .. Ada ide? [NSConcreteNotification allKeys]: pemilih yang tidak dikenal dikirim ke instance 0x1fd87480 2013-07-05 16: 10: 36.469 Providence [2961: 907] *** Mengakhiri aplikasi karena pengecualian yang tidak tertangkap 'NSInvalidArgumentException', alasan: '- [NSConcreteNotification allKeys]: alasan:' - [NSConcreteNotification allKeys]: alasan: pemilih dikirim ke instance 0x1fd87480 '
Awais Tariq
@AwaisTariq - Hmmm - tebakan saya adalah bahwa objek yang dilewatkan oleh iOS ke didReceiveRemoteNotification sebenarnya bukan NSDictionary, seperti ditentukan oleh antarmuka.
Aneil Mallavarapu
Bagaimana jika pengguna belum menavigasi ke kelas pengamat Anda? : /
halbano
15

Kode

Berikut ini pendekatan menggunakan sintaks kasus sakelar hebat di 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
        }
    }
}

Ide dasarnya sama dengan jawaban zirinisp, hanya menggunakan sintaksis Swift 3+ yang lebih.


Pemakaian

Anda mungkin ingin membuat file dengan nama UIWindowExtension.swift. Pastikan itu termasuk import UIKitpernyataan, sekarang salin kode ekstensi di atas .

Di sisi panggilan dapat digunakan tanpa pengontrol tampilan tertentu :

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

Atau jika Anda tahu pengontrol tampilan Anda dapat dijangkau dari pengontrol tampilan tertentu :

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

Saya harap ini membantu!

Astaga
sumber
Kasus ketiga akan macet karena rekursi tak terbatas. Cara mengatasinya adalah mengubah nama vc menjadi presentingViewControllerdan meneruskan presentingViewController.presentedViewControllersebagai parameter ke metode rekursif.
Ikhsan Assaat
Saya tidak mengerti, maaf. Maksudmu UIWindow.visibleViewController(from: presentedViewController)mestinya sebaliknya UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut
benar, presentedViewControllerdan viewControllermerupakan objek yang sama dan itu akan memanggil metode itu sendiri sampai stack meluap (pun intended). Jadi itu akan terjadi case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat
1
Solusi ini berfungsi ketika yang lain tidak. Anda harus memperbarui ke Swift 5. Pada dasarnya tidak ada perubahan. Perbarui saja tajuk untuk jawaban Anda.
TM Lynch
14

Saya telah menemukan bahwa iOS 8 telah mengacaukan semuanya. Di iOS 7 ada yang baruUITransitionView hirarki tampilan yang baru setiap kali Anda disajikan secara moderat UINavigationController. Bagaimanapun, ini kode saya yang menemukan mendapat VC paling atas. Panggilan getTopMostViewControllerharus mengembalikan VC yang seharusnya dapat Anda kirimi pesan presentViewController:animated:completion. Tujuannya adalah untuk memberi Anda VC yang dapat Anda gunakan untuk menyajikan modal VC, sehingga kemungkinan besar akan berhenti dan kembali di kelas wadah seperti UINavigationControllerdan BUKAN VC yang terkandung di dalamnya. Seharusnya tidak sulit untuk mengadaptasi kode untuk melakukannya juga. Saya telah menguji kode ini dalam berbagai situasi di iOS 6, 7 dan 8. Tolong beri tahu saya jika Anda menemukan 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;
}
nvrtd frst
sumber
Tolong jangan duplikat jawaban - tandai pertanyaan sebagai duplikat jika ya, atau jawab masing-masing pertanyaan dengan jawaban spesifik yang layak mereka dapatkan jika tidak duplikat.
Flexo
13

Kode jauh lebih sedikit daripada semua solusi lain:

Versi objektif-C:

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

    return topViewController;
}

Versi Swift 2.0: (kredit diberikan ke Steve.B)

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

Bekerja di mana saja di aplikasi Anda, bahkan dengan modals.

jungledev
sumber
1
Ini tidak menangani situasi di mana pengontrol tampilan yang disajikan adalah UINavigationControlleryang memiliki anak sendiri.
levigroker
@levigroker, mungkin itu cara Anda merancang pandangan Anda? Ini berfungsi dengan baik bagi saya untuk menggunakan ini dengan Nav. (
Begitulah
@jungledev Saya yakin Anda benar. Yang mengatakan, solusi yang bekerja di semua konfigurasi tampilan controller adalah apa yang dibutuhkan.
levigroker
@levigroker itu tidak bekerja di semua vc standar configurations- aplikasi saya bekerja pada memiliki arsitektur yang sangat kompleks, digunakan oleh lebih dari 500k pengguna, dan karya-karya ini di mana-mana di app. Mungkin Anda harus memposting pertanyaan yang menanyakan mengapa itu tidak berhasil dalam pandangan Anda, dengan contoh kode?
jungledev
jungledev Saya senang kode ini cocok untuk Anda, tetapi sepertinya itu bukan solusi yang lengkap. Jawaban @ zirinisp bekerja dengan baik dalam situasi saya.
levigroker
8

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

Pemakaian:

 if let topController = window.visibleViewController() {
            println(topController)
        }
Bobj-C
sumber
Ini as!dan navigationController.visibleViewController!untuk Swift 2.0
LinusGeffarth
7

Tentukan judul untuk setiap ViewController dan kemudian dapatkan judul ViewController saat ini dengan kode yang diberikan di bawah ini.

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

Kemudian periksa dengan judul Anda seperti ini

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}
Neel Kamal
sumber
Dfntly jawaban terbaik, Anda juga dapat memberi nama viewController Anda dengan:self.title = myPhotoView
Resty
5

Punyaku lebih baik! :)

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
sumber
4

Mengapa tidak menangani kode pemberitahuan push saja di delegasi aplikasi? Apakah ini terkait langsung dengan tampilan?

Anda dapat memeriksa apakah tampilan UIViewController saat ini terlihat dengan memeriksa apakah windowproperti view-nya memiliki nilai. Lihat lebih lanjut di sini .

Dima
sumber
Ya, ini terkait dengan tampilan, karena saya harus menunjukkan tampilan lencana. biarkan saya memeriksa tautannya. terima kasih :)
lu yuan
4

Cukup tambahkan jawaban @zirinisp.

Buat file, beri nama UIWindowExtension.swiftdan rekatkan cuplikan berikut:

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
}

Gunakan di mana saja sebagai:

if let topVC = getTopViewController() {

}

Terima kasih kepada @zirinisp.

Ashok
sumber
3

Mengenai Posting NSNotificationCenter di atas (maaf tidak dapat mengetahui di mana memposting komentar di bawahnya ...)

Dalam kasus beberapa mendapatkan - [NSConcreteNotification allKeys] semacam kesalahan. Ubah ini:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

untuk ini:

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

Ini berhasil untuk saya. Saya memiliki banyak target yang memiliki pengontrol yang berbeda sehingga jawaban sebelumnya sepertinya tidak berfungsi.

pertama Anda ingin ini di dalam kelas AppDelegate Anda:

var window: UIWindow?

kemudian, dalam fungsi Anda

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

Ini adalah cara terbaik yang pernah saya coba. Jika itu harus membantu siapa pun ...

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

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

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

Dengan ini, Anda dapat dengan mudah mendapatkan pengontrol tampilan pos atas seperti itu

let viewController = UIApplication.topMostViewController

Satu hal yang perlu diperhatikan adalah jika ada UIAlertController yang sedang ditampilkan, UIApplication.topMostViewControllerakan mengembalikan a UIAlertController.

NSEeksepsi
sumber
1

Jawaban cepat versi jungledev

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

Saya membuat kategori untuk UIApplicationdengan visibleViewControllersproperti. Gagasan utamanya cukup sederhana. Aku bimbang viewDidAppeardan viewDidDisappearmetode dalam UIViewController. Dalam viewDidAppearmetode viewController ditambahkan ke stack. Dalam viewDidDisappearmetode viewController dihapus dari tumpukan. NSPointerArraydigunakan bukan NSArrayuntuk menyimpan yang lemahUIViewController referensi . Pendekatan ini berfungsi untuk semua hierarki viewControllers.

Aplikasi UIA + VisibleViewControllers.h

#import <UIKit/UIKit.h>

@interface UIApplication (VisibleViewControllers)

@property (nonatomic, readonly) NSArray<__kindof UIViewController *> *visibleViewControllers;

@end

Aplikasi UIA + 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

Versi Swift 3

Aplikasi UIA + 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
sumber
1

Selalu periksa konfigurasi build Anda jika Anda menjalankan aplikasi dengan debug atau rilis.

CATATAN PENTING: Anda tidak dapat mengujinya tanpa menjalankan aplikasi dalam mode debug

Ini solusi saya

Vikram Sinha
sumber