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

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

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

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

AppDelegate.м

...
- (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.м

- (void)handleThePushNotification:(NSDictionary *)userInfo{

    //set some badge view here

}

19 ответов


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

UIViewController *vc = self.window.rootViewController;

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

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

EDIT:

если вы хотите, чтобы верхний посмотреть (не смотреть контроллер), вы можете проверить

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

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

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


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

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

UIWindow + PazLabs.H (заголовочный файл)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.м (выполнения файл)

#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

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

вы также можете отправить уведомление через 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
   }
}

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


простое расширение для 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
    }

работает идеально:-)


код

вот подход, использующий великий синтаксис switch-case на Swift 3:

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, это просто использование более быстрого 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`
}

надеюсь, это поможет!


я обнаружил, что 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;
}

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

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
}

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


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

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

затем проверьте его по названию, как это

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

ответ зириниспа в 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)
        }

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

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


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

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

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

для этого:

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

просто дополнение к ответу @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.


Мой лучше! :)

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

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

сначала вы хотите это внутри вашего класса 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!")    
   }
}

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

С этим вы можете легко получить контроллер верхнего вида post, Как так

let viewController = UIApplication.topMostViewController

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


Swift 2.0 версия ответа юнгледева

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

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

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

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

    return topController;
}

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

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

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


Я создал категорию для UIApplication С visibleViewControllers собственность. Основная идея довольно проста. Я swizzled 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.м

#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.Свифт!--17-->

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