收藏iosiOS

iOS 响应者及响应者链

2019-06-27  本文已影响0人  Vergil_wj

当我们点击一个 button 时,button 的响应消息机制分为两块:

UIButton 继承关系:

UIButton < UIControl < UIView < UIResponder

UIButton 之所以能够处理事件,是因为它继承自 UIResponder。也就是说只有继承自UIResponder的类才能处理事件

找响应者

如图,找响应者是从父 View 到子 View 过程查找。主要用到了 UIView 的hitTest:withEvent: 以及 pointInside:withEvent: 两个方法。

原理如下:

hitTest:withEvent: 方法处理机制:

当前 view 调用自身的 pointInside: withEvent:方法判断触摸点是否在自己范围内:

举个例子,更加清晰的了解下:

如图:

当用户点击ViewD所在的区域时会进行以下hit-Testing:

需要注意的地方:

1、hitTest 方法调用 pointInside 方法;

2、hit-Testing 过程是从 superView 向 subView 逐级传递,也就是从层次树的根节点向叶子节点传递;

3、遇到以下设置时,view 的 pointInside 将返回NO,hitTest 方法返回 nil:

事件响应

在上一部分已经找到了响应者,这个响应者就会执行相应的 touch 系列方法,系统默认处理事件之后将不继续向下一响应者传递。我们自己可以根据需要,通过复写方法把当前事件向下一响应者进行传递。

可以复写下列方法对事件进行处理

//触摸开始,手指触碰屏幕
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//触摸结束,手指离开屏幕
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//触摸取消(如电话接入的时候)
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//手指移动(会调用多次)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//3D touch 9.1之后加入的3D触摸事件
- (void)touchesEstimatedPropertiesUpdated:(NSSet *)touches

举个例子:

新建一个 Single View App,在 ViewController.m 中:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //加上这一句,事件就可以向下一个响应者传递
    [super touchesBegan:touches withEvent:event];
    
    NSLog(@"viewController touch begin");
}

在 AppDelegate.m 中:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"appDelegate touch begin");
}

这样我们就实现了将事件向下一个响应者传递。

响应者链

下图响应者链链来自官网

我们也可以通过代码打印响应者链:

- (IBAction)click:(id)sender {
    UIResponder *res = sender;
    
    while (res) {
        NSLog(@"*************************************\n%@",res);
        res = [res nextResponder];
    }
}

解释一下:

1、如果 hit-test view 或 first responder 不处理此事件,则将事件传递给其 nextResponder 处理,若有 UIViewController 对象则传递给 UIViewController,传递给其 superView。

2、如果 view 的 viewController 也不处理事件,则 viewController 将事件传递给其管理 view 的 superView。

3、视图层级结构的顶级为 UIWindow 对象,如果 window 仍不处理此事件,传递给 UIApplication.

4、若 UIApplication 对象不处理此事件,则事件被丢弃。

了解响应者链有时候可以帮我解决一些实际问题。我举个例子,我们知道,当提供给你一个ViewController你可以很容易得到它的view,一句代码的事情:

viewWanted = someViewController.view;

但如果反过来呢?当给你一个view,让你找到其所在的ViewController呢?这时候响应者链可以帮上忙了,代码如下:

@implementation UIView (FindController)
-(UIViewController*)parentController{
    UIResponder *responder = [self nextResponder];
    while (responder) {
    if ([responder isKindOfClass:[UIViewController class]]) {
        return (UIViewController*)responder;
    }
    responder = [responder nextResponder];
    }
    return nil;
}
@end

放一张完整的图来理解下iOS触摸事件的流动

参考资料:

官方文档

https://www.cnblogs.com/Quains/p/3369132.html

https://www.cnblogs.com/wengzilin/p/4720550.html

http://shellhue.github.io/2017/03/04/FlowOfUITouch/

上一篇 下一篇

猜你喜欢

热点阅读