总体来说,分2个步骤:

一,从上到下寻找合适的控件来处理这个触摸事件。如下图,如果点击了黄色4,则UIApplication -> UIWindow -> 1白色 -> 2橙色 -> 3蓝色 -> 4黄色。

二,找到4黄色后,再从下到上遍历响应者链条:4黄色 -> 3蓝色 -> 2橙色  -> 1白色  -> UIWindow  -> UIApplication

1)如果4黄色实现了touches...这些函数(具体下面第二条)且没有调用 super...则事件不再向上传递;如果调用了super...方法则事件会继续向上传递。

2)如果4黄色没有实现touches...这些函数,则直接向上传递。

详细介绍:

1 什么是响应者对象

在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”

2 触摸事件常用处理方法

  • -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;一根或者多根手指开始触摸view
  • -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;一根或者多根手指在view上移动(只有产生一定的位移才会调用)
  • -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;一根或者多根手指离开view,系统会自动调用view的下面方法
  • -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程

提示:touches中存放的都是UITouch对象

3  UITouch对象

当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象,一个手指对应一个UITouch对象。如果有两个手指则会有UITouch对象,因此可以根据(NSSet *)touches的个数来判断有几个手指。

作用:

  • 保存着跟手指相关的信息,比如触摸的位置、时间、阶段;
  • 当手指移动时,系统会更新同一个UITouch对象;
  • 当手指离开屏幕时,系统会销毁相应的UITouch对象。

4  UIView不接收触摸事件的三种情况

  • userInteractionEnabled = NO
  • hidden = YES
  • alpha = 0.0 ~ 0.01

提示:UIImageView的userInteractionEnabled默认是NO,因此它和它的子控件默认不能接收触摸事件。

5  寻找合适的响应者

从上到下寻找合适的控件,比如UIApplication -> UIWindow ->父控件子控件这样一级一级的找下去。

寻找顺序,或原则

  • 1> 自己是否能接收触摸事件?否,事件传递到此结束;
  • 2> 触摸点是否在自己身上?否,事件传递到此结束。调用;
  • 3> 从后往前遍历子控件,子控件执行同样的查找 (所谓从后往前遍历:即后添加的控件优先级高。准确说是subviews数组后面的优先级高,如果是storyboard开发,则是下面的优先级高);
  • 4> 如果没有符合条件的子控件,那么就自己最适合处理。

调用的两个函数:- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

总结:很明显,如果父控件不能接收,则不再找子控件。

  用代码表示如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {// 自己能否处理触摸事件:三种判断if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {return nil;}// 是否在自己身上if (![self pointInside:point withEvent:event]) {return nil;}// 从后往前遍历子控件,子控件执行同样的查找:hitTest方法for (NSInteger i = self.subviews.count - 1; i >= 0; i--) {UIView *subView = self.subviews[i];CGPoint subPoint = [self convertPoint:point toView:subView];UIView *fitView = [subView hitTest:subPoint withEvent:event];if (fitView) {return fitView;}}// 如果没有则自己是合适的return self;
}

  下面举个常见例子,如下图所示:层级关系A->B->C,当点击箭头所指时,B也能响应。

  UI代码简单如下:

- (void)viewDidLoad {[super viewDidLoad];ViewA *a = [[ViewA alloc] initWithFrame:CGRectMake(50 , 50, 300, 200)];a.backgroundColor = [UIColor redColor];
//    a.clipsToBounds = YES;
    ViewB *b = [[ViewB alloc] initWithFrame:CGRectMake(50, 50, 120, 300)];b.backgroundColor = [UIColor greenColor];ViewC *c = [[ViewC alloc] initWithFrame:CGRectMake(50, 10, 50, 100)];c.backgroundColor = [UIColor blueColor];[a addSubview:b];[b addSubview:c];[self.view addSubview:a];
}

  ViewA,ViewB,ViewC为自定义view继承自UIview,分别实现他们的touchesBegan方法,用于验证是否得到响应,比如ViewA,

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    [super touchesBegan:touches withEvent:event];NSLog(@"A");
}

  具体做法就是:实现A的hitTest方法,只是将是否在自己身上的判断移到了后面,另外加了个clipsToBounds的判断(B超出的部分没有显示出来则不应该响应)。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {// 三种判断if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {return nil;}// 如果子view没有显示出来,则直接进行pointInside判断if (self.clipsToBounds && ![self pointInside:point withEvent:event]) {return nil;}// 从后往前遍历子控件,子控件执行同样的查找:hitTestfor (NSInteger i = self.subviews.count - 1; i >= 0; i--) {UIView *subView = self.subviews[i];// 坐标系的转换CGPoint subPoint = [self convertPoint:point toView:subView];UIView *fitView = [subView hitTest:subPoint withEvent:event];if (fitView) {return fitView;}}if (![self pointInside:point withEvent:event]) {return nil;}// 如果没有则自己是合适的return self;
}

6  响应者链条

从下到上寻找合适的控件来处理这个事件,如果实现了touches。。。函数且没有调用super方法则事件不再向上传,如果没有实现touches或者在touches里面调用了super方法则事件继续向上传。(super不是指的父类,而是上一个响应者,nextResponder),当层级关系确定后,nextResponder也就确定了。

提示:如果当前的view是控制器的view,那控制器就是上一级响应者,否则上一级响应者为父控件。

7 总结:

假设用户触摸ViewC,runloop被唤醒,并把触摸事件放入UIApplication队列,处理事件时,将事件发给keyWindow,然后:

* 走hittest逻辑,即A->B->C的hittest都会被调用,最终获取事件的第一响应者viewC。
* viewC所在的响应者链条上,从它自己开始到next responder最后一个view,如果绑定有UIGestureRecognizer,则优先发给UIGestureRecognizer,然后再发给touchesBegan,viewC上绑定的UIGestureRecognizer优先识别,如果识别成功,则会取消调用touches的后续方法,在识别成功之前的touches还是会得到调用。
* 手势默认都是互斥的,viewC成功,则viewA,viewB上绑定的手势识别失败,不会响应。
* 如果viewC非UIControl子类,viewC上手势没有识别成功,则会再识别viewB,依次沿着响应链优先级递减
* 如果viewC是UIControl子类,如果viewC上手势识别成功,UIControl的action就不会响应,如果viewC上手势识别失败UIControl的action会得到响应。无论viewC手势是否识别成功,均不会再识别后续响应链上的手势。

假设viewC非UIControl子类,viewB,viewC上的手势也想识别成功,则只需将viewB,viewC

```
UITapGestureRecognizer *ges = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gesTap)];
[viewB addGestureRecognizer:ges];
ges.delegate = viewB;
/// 代理方法返回YES,可以同时识别,非互斥
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
```

注: 每一次触摸都有UITouch对象与之对应,UITouch对象可以在touches方法中拿到。响应链上绑定的所有手势存在 UITouch->_gestureRecognizers 数组中

对于UIControl子类,它的事件识别是基于4个touch方法实现的,如果你重新了其中一个,则UIControl的事件就不会得到响应。或者你将4个都实现,然后调用super方法也可以响应

转载于:https://www.cnblogs.com/mddblog/p/4399670.html

iOS中响应者链条-触摸事件,hitTest方法坐标转换相关推荐

  1. java swing 注册事件_比较Java Swing中三种注册事件的方法

    Swing 是目前Java中不可缺少的窗口工具组,是建立图形化用户界面(GUI)程序的强大工具.Java Swing组件自动产生各种事件来响应用户行为.Java将事件封装成事件类,并且为每个事件类定义 ...

  2. IOS中Json解析的四种方法

    2019独角兽企业重金招聘Python工程师标准>>> 作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有的json代码格式比较混乱,可以使用此& ...

  3. 【转】IOS中Json解析的四种方法

    原文网址:http://blog.csdn.net/enuola/article/details/7903632 作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有 ...

  4. iOS中创建动态库及调用方法

    去年因需要用到动态库,自己就找了好多一些 资料,最终找到了一套方法,怎么创建与使用动态库,记录一下: Xcode提供了在iOS工程中创建静态库的功能,和在MAC上创建动态库和静态库的功能. 但是没有提 ...

  5. iOS 中生成随机数的4种方法(rand、random、arc4random、arc4random_uniform)

    iOS 有如下四种随机数方法,下面以产生 [0,100) 的随机数为例: 1. srand((unsigned)time(0));  //不加这句每次产生的随机数不变 int i = rand() % ...

  6. ios json包含html,IOS中Json解析的四种方法

    发现自己有很多文档,所以现在整理一下,以防忘了... 作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有的json代码格式比较混乱,可以使用此"http: ...

  7. iOS中AppTrackingTransparency(ATT)设置方法和注意事项

    AppTrackingTransparency demo:https://github.com/Jdb156158/AppTrackingTransparency.git iOS 14 Checkli ...

  8. IOS中忽略警告的三种方法

    开发中xcode常常会提示一些警告,有些警告需要我们注意,并修改我们的代码,但有些警告又不是修改代码就可以去除的,对于有强迫症,容不得警告存在的程序猿来说,就是让人抓狂的一件事了.本文会详细讲解如何忽 ...

  9. iOS中安全结束 子线程 的方法

    一个典型的结束子线程的方法:   用 isFinished 检测子线程是否被完全kill掉 -(IBAction)btnBack:(id)sender {//释放内存 仅仅remove 并不会触发内存 ...

最新文章

  1. BAT某家应届研究生吐槽互联网实习经历:宁愿降薪回老家,也不屈服996
  2. Android SDK安装找不到JDK
  3. Where Should an Architect Begin?--reference
  4. BUUCTF-Reverce:不一样的flag
  5. Spark加载hadoop配置原理
  6. 预告:公共语言运行库(CLR)开发系列课程(4):COM Interop进阶
  7. 查看MySQL数据库中每个表占用的空间大小
  8. c++扫雷游戏代码_C语言学习教程,用C语言编写扫雷游戏
  9. 大道至简——书摘与思考
  10. ORACLE语句大全
  11. 编程算法 - 赛马问题
  12. 广州宽带市场割喉战:电信地狱价小企业陷两难
  13. 【无题】2022-1
  14. drawio界面自定义配置
  15. C# 阿里云对象存储OSS创建、删除、上传代码实现
  16. ISD9160学习笔记01_大联大Nuvoton ISD9160语音识别开发板初体验
  17. 为什么老派摄影师认为您只是个时髦的时髦
  18. vue router连续点击多次路由报错
  19. 阿里云天池机器学习task1
  20. 希腊字母大小写及其读音、英文

热门文章

  1. Ubuntu虚拟机登录密码忘记,修改登录密码方法
  2. windows10 Ubuntu子系统忘记密码,亲测有效
  3. java 1+1 =3
  4. Tomcat介绍 安装jdk 安装Tomcat
  5. 面向 IoT 物联网的架构设计参考
  6. VUEJS项目实践四之自定义键盘指令(按键即获取焦点)
  7. 区块链的数据结构和数据存储
  8. 分布式系统——CAP定理
  9. Achuan读论文:PARE:一种用于单语言和多语言远程监督关系抽取的简单而又强大的基线
  10. HC32L110(二) HC32L110 在 Ubuntu 下使用 J-Link 烧录