iOS 获取当前正在显示的ViewController
适用范围,在APPDelegate中需要获取当前显示的vc(比如来了新的推送), tabbar的子视图都是NavigationController,其它情况可以根据情况调整
+ (UIViewController *)getCurrentVC{
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
//app默认windowLevel是UIWindowLevelNormal,如果不是,找到UIWindowLevelNormal的
//其他框架可能会改我们的keywindow,比如支付宝支付,qq登录都是在一个新的window上,这时候的keywindow就不是appdelegate中的window。 当然这里也可以直接用APPdelegate里的window。
if (window.windowLevel != UIWindowLevelNormal)
{
NSArray *windows = [[UIApplication sharedApplication] windows];
for(UIWindow * tmpWin in windows)
{
if (tmpWin.windowLevel == UIWindowLevelNormal)
{
window = tmpWin;
break;
}
}
}
UIViewController* currentViewController = window.rootViewController;
while (YES) {
if (currentViewController.presentedViewController) {
currentViewController = currentViewController.presentedViewController;
} else {
if ([currentViewController isKindOfClass:[UINavigationController class]]) {
currentViewController = ((UINavigationController *)currentViewController).visibleViewController;
} else if ([currentViewController isKindOfClass:[UITabBarController class]]) {
currentViewController = ((UITabBarController* )currentViewController).selectedViewController;
} else {
break;
}
}
}
return currentViewController;
}
这里讲一下实现思路, 我们想要与控制器无耦合的情况下, 想要直接获取到当前控制器, 基本都是通过 rootViewController 来查找的, 通过上面的方法拿到 rootViewControoler 之后, 我们先看 presentedViewController, 因为控制器呈现出来的方式有 push 与 present, 我们先查看它是否是 present 出来的, 如果是则通过此属性能找到 present 出来的当前控制器, 然后在检查是否属于 UINavigationControler 或 UITabBarController ,如果是则通过查找其子控制器里面*顶层或者其正在选择的控制器。 *后在判断当前控制器是否有子控制器的情况, 如果有则取其子控制器*顶层, 否则当前控制器就是其本身。
这里主要是查找当前 应用程序基于 UITabBarController 和 UINavigationControler 下管理的视图控制器, 如果还有其他控制器则需要添加 if 条件来进行判断。
presentedViewController
Apple 文档 presentedViewControlle
通过此方法可以查找到通过 presented 模态方式(显示与隐士) 方式推出的当前控制器。 例如: AViewController –> BViewController 通过模态方式推出. 则使用 AViewController.presentedViewController 能获取到 BViewController。
presentingViewController
Apple 文档
通过此方法可以查找到通过 presented 模态方式(显示与隐士) 方式推出当前控制器的上层控制器。 例如: AViewController –> BViewController 通过模态方式推出. 则使用 BViewController.presentingViewController 能获取到 AViewController。
还有一种写法 , 在一个普通的UIView或者UIView子类中,需要找到离这个View*近的ViewController , 可以写一个类别
#import <UIKit/UIKit.h>
@interface UIView (ViewController)
– (UIViewController *)viewController;
@end
#import “UIView+ViewController.h”
@implementation UIView (ViewController)
– (UIViewController *)viewController
{
UIResponder *next = [self nextResponder];
do {
if ([next isKindOfClass:[UIViewController class]]) {
return (UIViewController *)next;
}
next = [next nextResponder];
} while (next != nil);
return nil;
}
@end
– (nullableUIResponder*)nextResponder 解释
问题1。 如何调用父view的controller里面的方法?
答案如下:
[[self superview ].nextResponder method];
[[[self superview ] nextResponder] method];
[self.nextResponder method];
上面的都可以,看情况使用,使用的时候*好判断一下。
官方解释
UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t); UIViewController implements the method by returning its view’s superview; UIWindow returns the application object, and UIApplication returns nil.
UIView实现了这个方法通过返回对应的ViewController或者这个view的父视图;
UIViewController实现了这个方法返回这个view的父视图;
UIWindow返回UIApplication对象;
UIApplication 返回nil。
问题2:当一个子view需要接收点击事件,而父view也需要接收点击事件, 如何做?
当然, 你可能会说直接调用mysubview.superView即可, 这样做也确实是可以做到,但有时子view是不一定知道有这个特定的父view的存在的,如动态添加子view。
所以这里就可以用到消息响应链拉技术。
下面要做的也就是,让子view接收这些事件后,同时把这些事件继续向上传,会一直传到UIApplication为止。 而在传的过程中,如果子view接收了这些事件,那么事件会自然终止,我们现在可以做的是同时让子view接收事件,而且还让事件不终止,并继续向上传。
摘取一部分说明:
“当用户 与 iPhone的触摸屏 产生 互动时,硬件 就会探测到 物理接触 并且 通知 操作系统。接着 操作系统 就会创建 相应的事件 并且 将 其 传递给 当前正在运行的应用程序的事件队列。然后 这项事件 会被事件循环 传递给 优先响应者物件。优先响应者物件 是 事件 被触发时 和 用户 交互的物件,比如 按钮物件、视图物件。如果 我们 编写了 代码 让 优先响应者 处理 这种类型的事件,那么 它 就会处理 这种类型的事件。处理完 某项事件后,响应者 有 两个选项:1、将 其 丢弃;2、将 其 传递给 响应链条中的下一个响应者。下一个响应者的地址 存储 在当前响应者物件所包含的变量nextResponder当中。如果 优先响应者 无法处理 一项事件,那么 这项事件 就传递给 下一个响应者,直到 这项事件 到达 能处理它的响应者 或者 到达 响应链条的末端,也就是 UIApplication类型的物件。UIApplication类型的物件 收到 一项事件后,也是 要么 处理,要么 丢弃。“
比如 有 一个视图物件,这个视图物件上 有 一个按钮物件。当用户 触摸 这个按钮物件时,作为优先响应者,这个按钮物件 就会收到 一项事件。如果 这个按钮物件 无法处理 这项事件,就会将 这项事件 传递给 视图物件。如果 视图物件 无法处理 这项事件,就会将 这项事件 传递给 视图控制器物件。以此类推。
应该注意的 是 当我们 在使用 响应链条时,一项事件 并不会自动地 从一个响应者 传递到 下一个响应者。如果 要将 一项事件 从一个响应者 传递到 下一个响应者,我们 必须编写 代码 才能办到。”
要做的如下:
子view的代码如下:
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 这里可以做子view自己想做的事,做完后,事件继续上传,就可以让其父类,甚至父viewcontroller获取到这个事件了
[[self nextResponder]touchesBegan:toucheswithEvent:event];
}
– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[[self nextResponder]touchesEnded:toucheswithEvent:event];
}
– (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[[self nextResponder] touchesCancelled:toucheswithEvent:event];
}
– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[[self nextResponder] touchesMoved:toucheswithEvent:event];
}
另外需要注意的是:在重写这几个方法时,*好保证这几个方法都重写,否则事件响应链可能会变混乱。这是我的猜测哈,没有实际验证过。
还有一种方法, cocoa在 app 生命周期变化是注册了系统级的通知,我们只需要在viewcontroller中监听一下就行,下面是常用通知的名字 , 还有很多, 写一个 command 进去看看吧 .
UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification