我们知道,对于一个视图控制器类UIViewController,可以通过[self view]来获取到它所管理的视图UIView,反过来,对于一个视图类UIView,我们怎么获取到它的UIViewController?
UIView继承自UIResponder,UIResponder有一个实例方法- (UIResponder *)nextResponder,该方法返回响应链(responder chain)上的下一个对象。
有关- (UIResponder *)nextResponder方法,更具体的说明可参考UIResponder Class Reference,主要有几个重点:
1)UIResponder类不会自动存储和设置它的下一个响应者(the next responder),默认情况下nextResponder方法放回nil;
2)UIResponder的子类必须重载nextResponder方法,设置下一个响应者。比如UIView类,当管理它的UIViewController对象存在时,返回该UIViewController,否则返回它的父视图(superview);UIViewController类返回它所管理的视图的父视图;UIWindow类返回应用对象(the application object);UIApplication类返回nil。
一、获取view的controller对象:
通过以上的分析,对于一个视图类UIView,我们可以利用- (UIResponder *)nextResponder方法遍历响应链,获取到它的UIViewController,具体代码:
- @implementation UIView (FindUIViewController)
- - (UIViewController *)viewController {
- /// Finds the view's view controller.
- // Traverse responder chain. Return first found view controller, which will be the view's view controller.
- UIResponder *responder = self;
- while ((responder = [responder nextResponder]))
- if ([responder isKindOfClass: [UIViewController class]])
- return (UIViewController *)responder;
- // If the view controller isn't found, return nil.
- return nil;
- }
- @end
- - (UIViewController *)getCurrentRootViewController {
- UIViewController *result;
- if (rootViewController)
- {
- // If developer provieded a root view controler, use it
- result = rootViewController;
- }
- else
- {
- // Try to find the root view controller programmically
- // Find the top window (that is not an alert view or other window)
- UIWindow *topWindow = [[UIApplication sharedApplication] keyWindow];
- if (topWindow.windowLevel != UIWindowLevelNormal)
- {
- NSArray *windows = [[UIApplication sharedApplication] windows];
- for(topWindow in windows)
- {
- if (topWindow.windowLevel == UIWindowLevelNormal)
- break;
- }
- }
- UIView *rootView = [[topWindow subviews] objectAtIndex:0];
- id nextResponder = [rootView nextResponder];
- if ([nextResponder isKindOfClass:[UIViewController class]])
- result = nextResponder;
- else if ([topWindow respondsToSelector:@selector(rootViewController)] && topWindow.rootViewController != nil)
- result = topWindow.rootViewController;
- else
- NSAssert(NO, @"ShareKit: Could not find a root view controller. You can assign one manually by calling [[SHK currentHelper] setRootViewController:YOURROOTVIEWCONTROLLER].");
- }
- return result;
- }
所以这里就可以用到消息响应链拉技术。
下面要做的也就是,让子view接收这些事件后,同时把这些事件继续向上传,会一直传到UIApplication为止。 而在传的过程中,如果子view接收了这些事件,那么事件会自然终止,我们现在可以做的是同时让子view接收事件,而且还让事件不终止,并继续向上传。
摘取一部分说明:
“当用户 与 iPhone的触摸屏 产生 互动时,硬件 就会探测到 物理接触 并且 通知 操作系统。接着 操作系统 就会创建 相应的事件 并且 将 其 传递给 当前正在运行的应用程序的事件队列。然后 这项事件 会被事件循环 传递给 优先响应者物件。优先响应者物件 是 事件 被触发时 和 用户 交互的物件,比如 按钮物件、视图物件。如果 我们 编写了 代码 让 优先响应者 处理 这种类型的事件,那么 它 就会处理 这种类型的事件。处理完 某项事件后,响应者 有 两个选项:1、将 其 丢弃;2、将 其 传递给 响应链条中的下一个响应者。下一个响应者的地址 存储 在当前响应者物件所包含的变量nextResponder当中。如果 优先响应者 无法处理 一项事件,那么 这项事件 就传递给 下一个响应者,直到 这项事件 到达 能处理它的响应者 或者 到达 响应链条的末端,也就是 UIApplication类型的物件。UIApplication类型的物件 收到 一项事件后,也是 要么 处理,要么 丢弃。“
比如 有 一个视图物件,这个视图物件上 有 一个按钮物件。当用户 触摸 这个按钮物件时,作为优先响应者,这个按钮物件 就会收到 一项事件。如果 这个按钮物件 无法处理 这项事件,就会将 这项事件 传递给 视图物件。如果 视图物件 无法处理 这项事件,就会将 这项事件 传递给 视图控制器物件。以此类推。
应该注意的 是 当我们 在使用 响应链条时,一项事件 并不会自动地 从一个响应者 传递到 下一个响应者。如果 要将 一项事件 从一个响应者 传递到 下一个响应者,我们 必须编写 代码 才能办到。”
要做的如下:
子view的代码如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 这里可以做子view自己想做的事,做完后,事件继续上传,就可以让其父类,甚至父viewcontroller获取到这个事件了
[[selfnextResponder]touchesBegan:toucheswithEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[[selfnextResponder]touchesEnded:toucheswithEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[[selfnextResponder] touchesCancelled:toucheswithEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[[selfnextResponder] touchesMoved:toucheswithEvent:event];
}
另外需要注意的是:在重写这几个方法时,最好保证这几个方法都重写,否则事件响应链可能会变混乱。这是我的猜测哈,没有实际验证过。