在AppDelegate.m的屏幕上获取当前显示的UIViewController

时间:2012-07-24 19:09:57

标签: iphone ios uiviewcontroller push-notification

屏幕上的当前UIViewController需要通过设置徽标视图来响应来自APN的推送通知。但是,如何才能获得方法UIViewController中的application:didReceiveRemoteNotificationAppDelegate.m

我尝试使用self.window.rootViewController获取当前显示的UIViewController,它可能是UINavigationViewController或其他类型的视图控制器。我发现visibleViewController的{​​{1}}属性可用于获取屏幕上的UINavigationViewController。但如果它不是UIViewController,我该怎么办?

任何帮助表示赞赏!相关代码如下。

AppDelegate.m

UINavigationViewController

ViewControllerA.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];
}
...

19 个答案:

答案 0 :(得分:97)

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

UIViewController *vc = self.window.rootViewController;

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

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

编辑:

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

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

虽然这个视图可能看不见甚至被其某些子视图覆盖......

再次,这取决于你的用户界面,但这可能会有所帮助......

答案 1 :(得分:95)

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

所以我在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

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

答案 2 :(得分:39)

Swift 中UIApplication的简单扩展(甚至关心iPhone上UITabBarController内的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
    }
}

答案 3 :(得分: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方法中处理add / remove观察者调用。

答案 4 :(得分: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;
}

答案 5 :(得分:14)

代码

这是 Swift 3 中使用 switch-case语法的方法:

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

我希望它有所帮助!

答案 6 :(得分:11)

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

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
}

即使使用模态,也可以在应用中的任何位置使用。

答案 7 :(得分:7)

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

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

然后按照你的标题检查

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

答案 8 :(得分:7)

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

答案 9 :(得分: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
        }
    }
}

答案 10 :(得分:4)

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

您可以通过检查视图的window属性是否具有值来检查UIViewController的视图当前是否可见。查看更多here

答案 11 :(得分:3)

这对我有用。我有许多具有不同控制器的目标,因此以前的答案似乎不起作用。

首先你要在AppDelegate类中使用它:

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

然后,在你的函数中

<asp:GridView ID="TPAnnuity_GridView" AllowSorting="true" AllowPaging="true" Runat="server"
    DataSourceID="TPAnnuity_SqlDataSource" DataKeyNames="AnnuityTotalPointsID" 
    AutoGenerateColumns="False" ShowFooter="true" PageSize="20">
<Columns>
    <asp:TemplateField HeaderText="Company" SortExpression="CompanyName" HeaderStyle-VerticalAlign="Bottom">
        <ItemTemplate>
            <asp:Label ID="Label11" runat="server" Text='<%# Bind("CompanyName") %>'></asp:Label>
        </ItemTemplate>
        <EditItemTemplate>
            <asp:DropDownList ID="EditACompanyID" runat="server" DataSource="<%# ddlCompanyDS %>" DataValueField="CompanyID" DataTextField="CompanyName" selectedValue='<%# Bind("CompanyID") %>'></asp:DropDownList>
        </EditItemTemplate>
        <FooterTemplate>
            <asp:DropDownList ID="NewCompanyID" runat="server" DataSource="<%# ddlCompanyDS %>" DataValueField="CompanyID" DataTextField="CompanyName"></asp:DropDownList>
            <asp:RequiredFieldValidator ID="RequiredFieldValidator11" runat="server" ControlToValidate="NewCompanyID" Display="Dynamic"  ForeColor="" ErrorMessage="You must enter a value. *" Enabled="false"></asp:RequiredFieldValidator>
        </FooterTemplate>
        <FooterStyle Wrap="False" />
    </asp:TemplateField>    
</Columns>
<EmptyDataTemplate>
    <br />
    <i>No Commission Data to display.</i>
    <br />
    <br />
</EmptyDataTemplate>
</asp:GridView>

答案 12 :(得分:3)

除了@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。

答案 13 :(得分:3)

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

如果有人得到 - [NSConcreteNotification allKeys]错误的排序。改变这个:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

到此:

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

答案 14 :(得分:2)

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

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

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

    return topController;
}

答案 15 :(得分: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

答案 16 :(得分:1)

swift 2.0版本的jungledev的答案

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

答案 17 :(得分:1)

我为UIApplication的{​​{1}}属性创建了一个类别。主要想法很简单。我在visibleViewControllers中调整了viewDidAppearviewDidDisappear个方法。在UIViewController方法中,viewController被添加到堆栈中。在viewDidAppear方法中,viewController从堆栈中删除。使用viewDidDisappear代替NSPointerArray来存储弱NSArray的引用。此方法适用于任何viewControllers层次结构。

<强>的UIApplication + VisibleViewControllers.h

UIViewController

<强>的UIApplication + VisibleViewControllers.m

#import <UIKit/UIKit.h>

@interface UIApplication (VisibleViewControllers)

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

@end

https://gist.github.com/medvedzzz/e6287b99011f2437ac0beb5a72a897f0

Swift 3版本

<强>的UIApplication + VisibleViewControllers.swift

#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/ee6f4071639d987793977dba04e11399

答案 18 :(得分:1)

如果您使用调试版或发布版运行应用程序,请务必检查构建配置。

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

这是我的解决方案