Отримайте поточне відображення UIViewController на екрані в AppDelegate.m


126

Поточний UIViewControllerна екрані повинен відповідати на push-сповіщення від APN, встановивши деякі перегляди значків. Але як я міг би отримати UIViewControllerв методі application:didReceiveRemoteNotification: від 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 надає вам можливість знати, який контролер є найвищим; ваш кореневий контролер повинен якось надавати ту саму інформацію. Загалом це не можна зробити, оскільки це суворо залежить від того, як ви створили свій інтерфейс, і немає явної ієрархії контролера (як це відбувається для представлень). Ви можете просто додати властивість до свого кореневого контролера та встановити його значення кожного разу, коли ви «натискаєте» новий контролер зверху.
серхіо

1
Поки значення постійно оновлюється, це здається гарним способом перейти і до мене.
Діма

4
Немає прямого способу потрапляння до контролера від UIViewекземпляра. rootViewControllerце НЕ обов'язково показаний в даний момент контролер. Це просто вгорі ієрархії перегляду.
Gingi

101

Я завжди люблю рішення, що стосуються категорій, оскільки вони є болтами і їх можна легко використовувати повторно.

Тому я створив категорію в UIWindow. Тепер ви можете зателефонувати vidViewController у 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
            }
        }
    }
}

2
як я можу використовувати це для швидкої версії?
Vijay Singh Rana

2
Я не можу зрозуміти ваше запитання. Скопіюйте та вставте всередину свого коду.
zirinisp

Що з користувацьким контейнером VC?
Мінгмінг

@Mingming не повинно бути таким складним, щоб додати додатковий, якщо перевірити, чи є його призначений для користувача контейнер VC (у методі getVisibielController), і якщо так, повернути "видимий" контролер, який, як правило, буде vc.childControllers.lastObject для більшості користувацьких реалізації контейнерів VC (я вважаю), але це залежатиме від того, як його реалізовано.
gadu

1
Я тільки що відправив відповідь з таким же підходом, що і в цій відповіді на оновлений синтаксис , за винятком: він використовує перемикач випадок і слід Swift 3 іменування конвенції: stackoverflow.com/a/42486823/3451975
Jeehut

43

Просте розширення для застосування UIA у Swift (дбає навіть про більшеNavigationController в UITabBarControlleriPhone) :

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController where top.view.window != nil {
                return topViewController(top)
            } else if let selected = tab.selectedViewController {
                return topViewController(selected)
            }
        }

        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }

        return base
    }
}

Просте використання:

    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

Дякуємо, що вказали на це - це має бути я. Я оновлю відповідь.
Aneil Mallavarapu

збій під час отримання всіх ключів від userInfo .. Будь-яка ідея? [NSConcreteNotification allKeys]: нерозпізнаний селектор, надісланий до екземпляра 0x1fd87480 2013-07-05 16: 10: 36.469 Провіденс [2961: 907] *** Завершення програми через невиконаний виняток 'NSInvalidArgumentException', причина: '- [NSConcreteNotification allKeys] селектор, надісланий екземпляру 0x1fd87480 '
Awais Tariq

@AwaisTariq - Hmmm - я здогадуюсь, що об'єкт, переданий iOS в didReceiveRemoteNotification, насправді не є NSDictionary, як вказує інтерфейс.
Aneil Mallavarapu

Що робити, якщо користувач ще не перейшов до вашого класу спостерігачів? : /
halbano

15

Код

Ось підхід із використанням синтаксису великого випадку перемикання у 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`
}

Я сподіваюся, що це допомагає!


Третій випадок вийде з ладу через нескінченну рекурсію. Виправлення полягає в перейменуванні 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. По суті, жодних змін. Просто оновіть заголовок для вашої відповіді.
ТМ Лінч

14

Я виявив, що iOS 8 все накрутив. У iOS 7 з'являється нова UITransitionViewієрархія перегляду кожного разу, коли у вас є модально представлені UINavigationController. У всякому разі, ось мій код, який знаходить, отримує найвищий VC. Виклик getTopMostViewControllerповинен повернути ВК, щоб ви могли надіслати повідомлення типу presentViewController:animated:completion. Його мета полягає в тому, щоб отримати вам ЦП, який ви можете використовувати для представлення модального ЦЗ, так що він, швидше за все, зупиниться і повернеться на контейнерні класи типу, UINavigationControllerа НЕ ВК, що містяться в них. Не слід важко адаптувати код і для цього. Я перевіряв цей код у різних ситуаціях в 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;
}

Будь ласка, не дублюйте відповіді - або позначайте ці питання як дублікати, якщо вони є, або відповідайте на окремі запитання конкретною відповіддю, яку вони заслуговують, якщо вони не є дублікатами.
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
}

Працює в будь-якій точці вашої програми, навіть у форматі.


1
Це не вирішує ситуацію, коли представлений контролер перегляду є a, UINavigationControllerякий має своїх дітей.
levigroker

@levigroker, можливо, це так, як ви архітектурували свої погляди? Для мене це чудово використовує це з Nav. (ось як я його використовую)
jungledev

@jungledev Я впевнений, що ти прав. Однак, рішення, яке працює у всіх конфігураціях контролера подання, - це те, що потрібно.
levigroker

@levigroker це робить роботу в усіх стандартних ВХ configurations- двотаврових роботу програми на має дуже складну архітектуру, використовується більш 500к користувачів, і це працює всюди в додатку. Можливо, вам слід надіслати запитання, чому воно не працює, на ваш погляд, із прикладами коду?
jungledev

jungledev Я щасливий, що цей код працює для вас, але він, здається, не є повноцінним рішенням. @ відповідь zirinisp чудово працює в моїй ситуації.
levigroker

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

Це 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.
  }
}

Насправді найкраща відповідь, також ви можете назвати свій погляд Контролер: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

Чому б не просто обробити код натискання сповіщень у делегата додатка? Це безпосередньо пов’язано з видом?

Ви можете перевірити, чи виглядає представлення 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.


3

Щодо публікації NSNotificationCenter вище (вибачте, не можу дізнатися, де розмістити коментар під нею ...)

У випадку, якщо деякі отримують - своєрідну помилку [NSConcreteNotification allKeys]. Змініть це:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

до цього:

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

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

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.


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

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

ВАЖЛИВА ПРИМІТКА. Ви не можете перевірити це, не запустивши додаток у режимі налагодження

Це було моє рішення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.