适用范围,在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