Получить текущий отображаемый UIViewController на экране в AppDelegate.m

126

Текущее UIViewControllerна экране необходимо для ответа на push-уведомления от APN, установив некоторые виды значков. Но как я могу получить UIViewControllerметод in application:didReceiveRemoteNotification: of AppDelegate.m?

Я попытался использовать self.window.rootViewControllerдля получения текущего отображения UIViewController, это может быть UINavigationViewControllerконтроллер представления или какой-то другой. И я обнаружил, что visibleViewControllerсвойство UINavigationViewControllerможно использовать для вывода UIViewControllerна экран. Но что я мог сделать, если это не а UINavigationViewController?

Любая помощь приветствуется! Соответствующий код выглядит следующим образом.

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

}
лу юань
источник

Ответы:

99

Вы также можете использовать, rootViewControllerесли ваш контроллер не является UINavigationController:

UIViewController *vc = self.window.rootViewController;

Если вы знаете контроллер корневого представления, это зависит от того, как вы построили свой пользовательский интерфейс, но вы, возможно, сможете найти способ навигации по иерархии контроллеров.

Если вы расскажете подробнее о том, как вы определили свое приложение, я мог бы дать еще несколько подсказок.

РЕДАКТИРОВАТЬ:

Если вам нужен самый верхний вид (а не контроллер просмотра), вы можете проверить

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

хотя это представление может быть невидимым или даже закрытым некоторыми из его подвидов ...

опять же, это зависит от вашего пользовательского интерфейса, но это может помочь ...

Сержио
источник
19
Проблема заключается в том, что видимое представление не принадлежит контроллеру корневого представления (в случае модальных представлений и т. Д.).
Дима
Да. Но это может быть UITabViewController. Нет ли прямого способа вывести UIViewController на экран?
lu yuan
2
ну, видите ли, UINavigationController дает вам возможность узнать, какой контроллер является самым верхним; ваш корневой контроллер должен каким-то образом предоставлять ту же информацию. Это невозможно сделать в целом, потому что это зависит строго от того, как вы построили свой пользовательский интерфейс, и нет явной иерархии контроллеров (как это происходит с представлениями). Вы можете просто добавить свойство к своему корневому контроллеру и установить его значение всякий раз, когда вы «нажимаете» новый контроллер сверху.
sergio
1
Пока стоимость поддерживается в актуальном состоянии, мне тоже кажется, что это хороший способ.
Дима
4
Прямого доступа к контроллеру из UIViewэкземпляра нет. rootViewControllerэто не обязательно показан в данный момент контроллер. Это просто наверху иерархии представлений.
Gingi
101

Мне всегда нравятся решения, которые включают категории, поскольку они закреплены и могут быть легко использованы повторно.

Итак, я создал категорию в UIWindow. Теперь вы можете вызвать visibleViewController в UIWindow, и это даст вам контроллер видимого представления, выполнив поиск по иерархии контроллеров. Это работает, если вы используете контроллер навигации и / или панели вкладок. Если у вас есть другой тип контроллера, пожалуйста, дайте мне знать, и я могу его добавить.

UIWindow + PazLabs.h (файл заголовка)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (файл реализации)

#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

Быстрая версия

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
источник
2
как я могу использовать это для быстрой версии?
Виджай Сингх Рана
2
Я не могу понять ваш вопрос. Скопируйте и вставьте в свой код.
zirinisp
А как насчет кастомного контейнера VC?
Mingming
@Mingming не должно быть так сложно добавить дополнительный компонент, если нужно проверить, является ли его пользовательский контейнер VC (в методе getVisibielController), и, если да, вернуть «видимый» контроллер, который обычно будет vc.childControllers.lastObject для большинства настраиваемых реализации контейнера VC (я полагаю), но будет зависеть от того, как он реализован.
gadu
1
Я только что отправил ответ с тем же подходом, что и в этом ответе, за исключением обновленного синтаксиса: он использует случай переключения и следует соглашениям об именах Swift 3: stackoverflow.com/a/42486823/3451975
Jeehut
43

Простое расширение для UIApplication в Swift (заботится даже о moreNavigationController внутри UITabBarControllerна 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
    }
}

Простое использование:

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

Отлично работает :-)

ОБНОВЛЕНИЕ для чистого кода:

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
Похоже, это код для Swift 2.x. Swift 3.x больше не знает «где». Кроме того, sharedApplication () теперь является «общим». Не так уж и важно. Обновление займет всего минуту. Было бы неплохо упомянуть, что он использует рекурсию. Кроме того, каждый вызов topViewController должен нуждаться в префиксе «base:».
Джефф Мьюир
37

Вы также можете отправить уведомление через NSNotificationCenter. Это позволит вам справиться с рядом ситуаций, в которых обход иерархии контроллеров представления может быть затруднительным - например, когда представлены модальные окна и т. Д.

Например,

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

В каждом из ваших контроллеров просмотра:

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

Вы также можете использовать этот подход для управления инструментами, которые должны обновляться при получении уведомления и используются несколькими контроллерами представления. В этом случае обработайте вызовы наблюдателя добавления / удаления в методах init и dealloc соответственно.

Анейл Маллаварапу
источник
1
Что addObserver:barвнутри viewDidLoad? Мне нужно заменить на self?
CainaSouza
Спасибо, что указали на это - он должен быть самим собой. Я обновлю ответ.
Анейл Маллаварапу
сбой при получении всех ключей от userInfo .. Есть идеи? [NSConcreteNotification allKeys]: нераспознанный селектор отправлен в экземпляр 0x1fd87480 2013-07-05 16: 10: 36.469 Providence [2961: 907] *** Завершение работы приложения из-за неперехваченного исключения 'NSInvalidArgumentException', причина: '- [NSConcreteNotification allKeys]: нераспознанный селектор отправлен в экземпляр 0x1fd87480 '
Авайс Тарик
@AwaisTariq - Хммм - я предполагаю, что объект, переданный iOS в didReceiveRemoteNotification, на самом деле не является NSDictionary, как указывает интерфейс.
Aneil Mallavarapu
Что, если пользователь еще не перешел в ваш класс наблюдателя? : /
halbano
15

Код

Вот подход, использующий отличный синтаксис switch-case в 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
        }
    }
}

Основная идея такая же, как и в ответе zirinisp, просто используется синтаксис, похожий на Swift 3+.


использование

Вероятно, вы захотите создать файл с именем UIWindowExtension.swift. Убедитесь, что он включает import UIKitинструкцию, а теперь скопируйте приведенный выше код расширения .

На стороне вызова его можно использовать без какого-либо конкретного контроллера представления :

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

Или, если вы знаете, что ваш контроллер видимого представления доступен с определенного контроллера представления :

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

Я надеюсь, что это помогает!

Jeehut
источник
В третьем случае произойдет сбой из-за бесконечной рекурсии. Исправление состоит в том, чтобы переименовать vc как presentingViewControllerи передать presentingViewController.presentedViewControllerв качестве параметра рекурсивному методу.
Ихсан Ассаат
Я не совсем понял, извините. Вы имеете в виду, UIWindow.visibleViewController(from: presentedViewController)что вместо этого должно быть UIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut
правильный, presentedViewControllerи viewControllerэто тот же объект, и он будет вызывать метод сам с собой, пока стек не переполнится (каламбур). Так и будет case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ихсан Ассаат
1
Это решение работало, когда другие не работали. Вам следует обновить до Swift 5. По сути, без изменений. Просто обновите заголовок своего ответа.
TM Lynch
14

Я обнаружил, что iOS 8 все испортила. В iOS 7 есть новое UITransitionViewв иерархии представлений всякий раз, когда у вас есть модальное представление UINavigationController. В любом случае, вот мой код, который находит, получает самый верхний VC. Вызов getTopMostViewControllerдолжен вернуть VC, который вы должны иметь возможность отправить сообщение вроде presentViewController:animated:completion. Его цель - предоставить вам VC, который вы можете использовать для представления модального VC, поэтому он, скорее всего, остановится и вернется в классы контейнера, такие как, UINavigationControllerа НЕ VC, содержащийся в них. Не должно быть сложно адаптировать код и для этого. Я тестировал этот код в различных ситуациях в iOS 6, 7 и 8. Сообщите мне, если вы обнаружите ошибки.

+ (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 первый
источник
Пожалуйста, не дублируйте ответы - либо отметьте вопросы как повторяющиеся, если они есть, либо ответьте на отдельные вопросы конкретным ответом, которого они заслуживают, если они не дублируются.
Flexo
13

Намного меньше кода, чем у всех других решений:

Версия Objective-C:

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

    return topViewController;
}

Версия Swift 2.0: (кредит принадлежит Стиву Б.)

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

Работает в любом месте вашего приложения, даже с модальными окнами.

jungledev
источник
1
Это не справляется с ситуацией, когда представленный контроллер представления UINavigationControllerимеет собственных дочерних элементов.
levigroker 03
@levigroker, может быть, ты так спроектировал свои взгляды? У меня отлично работает использовать это с Nav. (вот как я его использую)
jungledev 06
@jungledev Я уверен, что вы правы. Тем не менее, необходимо решение, которое работает во всех конфигурациях контроллера представления.
levigroker 06
@levigroker это делает работу во всех стандартных ВХ configurations- двутавровых работу приложения на имеет очень сложную архитектуру, используется более 500к пользователей, и это работает везде в приложении. Возможно, вам стоит задать вопрос, почему это не работает в вашем представлении, с примерами кода?
jungledev 06
jungledev Я рад, что этот код работает для вас, но, похоже, это не полное решение. Ответ @zirinisp отлично работает в моей ситуации.
levigroker 06
8

Ответ zirinisp на 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;
            }
        }
    }
}

Использование:

 if let topController = window.visibleViewController() {
            println(topController)
        }
BOBJ-С
источник
Это as!и navigationController.visibleViewController!для Swift 2.0
LinusGeffarth
7

Укажите заголовок для каждого ViewController, а затем получите заголовок текущего ViewController с помощью кода, приведенного ниже.

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

Затем проверьте это по своему заголовку, как это

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}
Нил Камаль
источник
Dfntly лучший ответ, также вы можете назвать свой viewController следующим образом:self.title = myPhotoView
Resty
5

Моя лучше! :)

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

Почему бы просто не обработать код push-уведомления в делегате приложения? Это напрямую связано с представлением?

Вы можете проверить, отображается ли в настоящее время представление UIViewController, проверив, имеет ли его windowсвойство представления значение. Подробнее здесь .

Дима
источник
Да, это связано с представлением, так как я должен показать представление значка. позвольте мне проверить ссылку. спасибо :)
lu yuan
4

Просто дополнение к ответу @zirinisp.

Создайте файл, назовите его UIWindowExtension.swiftи вставьте следующий фрагмент:

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
}

Используйте его где угодно как:

if let topVC = getTopViewController() {

}

Спасибо @zirinisp.

Ashok
источник
3

Что касается сообщения NSNotificationCenter выше (извините, не могу понять, где разместить под ним комментарий ...)

В случае, если некоторые получали ошибку - [NSConcreteNotification allKeys]. Измените это:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

к этому:

-(void)didReceiveRemoteNotification:(NSNotification*)notif {
NSDictionary *dict = notif.userInfo;
}
Bseaborn
источник
3

Это сработало для меня. У меня много целей с разными контроллерами, поэтому предыдущие ответы не сработали.

сначала вы хотите это внутри своего класса AppDelegate:

var window: UIWindow?

тогда в вашей функции

let navigationController = window?.rootViewController as? UINavigationController
if let activeController = navigationController!.visibleViewController {
    if activeController.isKindOfClass( MyViewController )  {
        println("I have found my controller!")    
   }
}
CodeOverRide
источник
2

Это лучший способ, который я пробовал. Если это кому-то поможет ...

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

С помощью этого вы можете легко получить контроллер верхнего представления, например

let viewController = UIApplication.topMostViewController

Следует отметить, что если в данный момент отображается UIAlertController, UIApplication.topMostViewControllerон вернет UIAlertController.

NSExceptional
источник
1

Swift 2.0 версия ответа jungledev

func getTopViewController() -> UIViewController {
    var topViewController = UIApplication.sharedApplication().delegate!.window!!.rootViewController!
    while (topViewController.presentedViewController != nil) {
        topViewController = topViewController.presentedViewController!
    }
    return topViewController
}
Стивен Б.
источник
1

Я создал категорию UIApplicationс visibleViewControllersнедвижимостью. Основная идея довольно проста. Я обвел viewDidAppearи viewDidDisappearметоды UIViewController. В viewDidAppearметоде viewController добавлен в стек. В viewDidDisappearметоде viewController удаляется из стека. NSPointerArrayиспользуется вместо NSArrayхранения слабыхUIViewController ссылки. Этот подход работает для любой иерархии 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

Версия 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

Всегда проверяйте конфигурацию сборки, если вы запускаете приложение с отладкой или выпуском.

ВАЖНОЕ ПРИМЕЧАНИЕ: вы не сможете протестировать его, не запустив приложение в режиме отладки.

Это было мое решение

Викрам Синха
источник