133

屏幕上的当前UIViewController需要通过设置一些徽章视图来响应来自 APN 的推送通知。但是我怎么能得到UIViewControllerin 方法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];
}
...

视图控制器A.m

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

    //set some badge view here

}
4

19 回答 19

107

我总是喜欢涉及类别的解决方案,因为它们是固定的并且可以很容易地重复使用。

所以我在 UIWindow 上创建了一个类别。您现在可以在 UIWindow 上调用 visibleViewController ,这将通过向下搜索控制器层次结构为您提供可见视图控制器。如果您使用导航和/或标签栏控制器,这将有效。如果您有其他类型的控制器要建议,请告诉我,我可以添加它。

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
            }
        }
    }
}
于 2014-02-18T08:41:33.663 回答
102

rootViewController当您的控制器不是时,您也可以使用UINavigationController

UIViewController *vc = self.window.rootViewController;

一旦你知道了根视图控制器,那么它取决于你如何构建你的 UI,但你可能会找到一种方法来浏览控制器层次结构。

如果您提供有关定义应用程序方式的更多详细信息,那么我可能会提供更多提示。

编辑:

如果你想要最顶层的视图(不是视图控制器),你可以检查

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

虽然这个视图可能是不可见的,甚至被它的一些子视图覆盖......

同样,这取决于您的用户界面,但这可能会有所帮助......

于 2012-07-24T19:14:37.477 回答
48

Swift 中 UIApplication 的简单扩展(甚至关心UITabBarControlleriPhone 上的 moreNavigationController)

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
    }
}
于 2015-02-24T14:59:25.060 回答
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 方法中处理添加/删除观察者调用。

于 2013-04-21T14:52:43.340 回答
20

代码

这是一种使用Swift 3/4/5中出色的switch-case 语法的方法:

import UIKit

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+ 的语法。


用法

您可能想要创建一个名为的文件并将UIWindowExt.swift上述扩展代码复制到其中。

在调用端,它可以在没有任何特定视图控制器的情况下使用:

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

我希望它有帮助!

于 2017-02-27T13:19:16.567 回答
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;
}
于 2014-09-19T23:15:53.963 回答
13

比所有其他解决方案更少的代码:

Objective-C 版本:

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

    return topViewController;
}

Swift 2.0 版本:(感谢 Steve.B)

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

可以在您的应用程序中的任何位置使用,即使使用模态也是如此。

于 2016-06-04T18:40:07.093 回答
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)
        }
于 2014-12-19T14:59:36.137 回答
7

为每个 ViewController 指定标题,然后通过下面给出的代码获取当前 ViewController 的标题。

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

然后像这样通过你的标题检查它

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}
于 2013-08-19T09:19:27.457 回答
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
        }
    }
}
于 2017-09-11T19:47:19.043 回答
4

为什么不在应用程序委托中处理推送通知代码?它与视图直接相关吗?

您可以检查 UIViewController 的视图当前是否可见,方法是检查它的视图的window属性是否有值。在这里查看更多。

于 2012-07-24T19:16:46.283 回答
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。

于 2016-03-03T13:34:22.373 回答
3

关于上面的 NSNotificationCenter 帖子(抱歉找不到在哪里发表评论...)

以防有些人收到 -[NSConcreteNotification allKeys] 错误。改变这个:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

对此:

-(void)didReceiveRemoteNotification:(NSNotification*)notif {
NSDictionary *dict = notif.userInfo;
}
于 2013-11-13T16:57:35.843 回答
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!")    
   }
}
于 2015-04-23T19:57:20.323 回答
2

这是我尝试过的最好的方法。如果它应该帮助任何人......

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

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

    return topController;
}
于 2016-11-23T09:51:09.020 回答
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.

于 2017-08-03T00:58:31.187 回答
1

Swift 2.0 版 Jungledev 的回答

func getTopViewController() -> UIViewController {
    var topViewController = UIApplication.sharedApplication().delegate!.window!!.rootViewController!
    while (topViewController.presentedViewController != nil) {
        topViewController = topViewController.presentedViewController!
    }
    return topViewController
}
于 2016-08-30T14:24:25.090 回答
1

UIApplication我为withvisibleViewControllers属性创建了一个类别。主要思想很简单。我在. viewDidAppear_ 在方法中 viewController 被添加到堆栈中。在方法 viewController 从堆栈中删除。用于代替存储弱的引用。这种方法适用于任何 viewControllers 层次结构。viewDidDisappearUIViewControllerviewDidAppearviewDidDisappearNSPointerArrayNSArrayUIViewController

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

斯威夫特 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

于 2016-11-28T10:08:55.687 回答
1

如果您正在运行带有调试或发布的应用程序,请务必检查您的构建配置。

重要提示:如果不在调试模式下运行您的应用程序,您将无法对其进行测试

这是我的解决方案

于 2018-04-20T11:46:45.237 回答