总体来说,分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 - ; i >= ; 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( , , , )];
a.backgroundColor = [UIColor redColor];
// a.clipsToBounds = YES; ViewB *b = [[ViewB alloc] initWithFrame:CGRectMake(, , , )];
b.backgroundColor = [UIColor greenColor]; ViewC *c = [[ViewC alloc] initWithFrame:CGRectMake(, , , )];
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;
}
// 从后往前遍历子控件,子控件执行同样的查找:hitTest
for (NSInteger i = self.subviews.count - ; i >= ; 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方法也可以响应

iOS中响应者链条-触摸事件,hitTest方法坐标转换的更多相关文章

  1. iOS中响应者链条-触摸事件

    总体来说,分2个步骤: 一,从上到下寻找合适的控件来处理这个触摸事件.如下图,如果点击了黄色4,则UIApplication -> UIWindow -> 1白色 -> 2橙色 -& ...

  2. iOS开发系列之触摸事件

    基础知识 三类事件中触摸事件在iOS中是最常用的事件,这里我们首先介绍触摸事件. 在下面的例子中定义一个KCImage,它继承于UIView,在KCImage中指定一个图片作为背景.定义一个视图控制器 ...

  3. iOS学习笔记之触摸事件&UIResponder

    iOS学习笔记之触摸事件&UIResponder 触摸事件 与触摸事件相关的四个方法如下: 一根手指或多根手指触摸屏幕 -(void)touchesBegan:(NSSet *)touches ...

  4. jquery中交替点击事件toggle方法的使用示例

    jquery中交替点击事件toggle方法中有两个参数,分别是要交替执行的事件.如果不传参默认是显示隐藏功能,下面有个不错的示例,感兴趣的朋友可以参考下 复制代码代码如下: $('#clickId‘) ...

  5. ios开发事件处理之 四:hittest方法的底层实现与应用

    #import "XMGWindow.h" /** 1:注意点:hitTest方法内部会调用pointInside方法,询问触摸点是否在自己身上,当遍历子控件时,传入的坐标点要进行 ...

  6. WPF中的多点触摸事件

    UIElement在WPF4下添加了很多支持多点触摸的事件,通过它们可以在硬件支持的情况下处理多点触摸,以下通过代码来说明通过处理这些事件,我们可以做些什么: 一.触摸相关的多种事件,跟鼠标事件是对应 ...

  7. C#中Form的Paint事件响应方法与重载虚方法OnPaint()的区别

    Form_Paint()方法是Paint事件的响应方法,OnPaint是可重载的虚方法,OnPaint方法是调用Paint事件的,用哪一个,效果是一样,就看那一个方便了内部是这样实现的: protec ...

  8. iOS中UI阶段常用的一些方法

    UI 即 UserInterface(用户界面 1.iOS系统版本,每年都有更新.对我们开发者而言,主要的是观察API的变化. 2.iPhone新手机发布,会产生不同尺寸的屏幕,现在市面上有4种尺寸, ...

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

    作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有的json代码格式比较混乱,可以使用此“http://www.bejson.com/”网站来进行JSON格式化校验 ...

随机推荐

  1. 第100天:CSS3中animation动画详解

    CSS3属性中有关于制作动画的三个属性:Transform,Transition,Animation: 一.Animation定义动画 CSS3的Animation是由“keyframes”这个属性来 ...

  2. HDU4788_Hard Disk Drive

    水题. 但是我写挫了一个地方,Wa了三发.好吧,不能忍了. 还有,本屌不知道如何用printf输出%,哪位学过C++的大仙知道这是什么情况?  告诉我一声啊. #include <iostrea ...

  3. bzoj1853-大包子的幸运数字

    题意 称只含有 6 和 8 的数字为幸运数字.称幸运数字的倍数为类幸运数字.求 \([l,r]\) 中有多少个类幸运数字.\(1\le l,r\le 10^{10}\) . 分析 幸运数字最多有 \( ...

  4. P1939 【模板】矩阵加速(数列)

    题目描述 a[1]=a[2]=a[3]=1 a[x]=a[x-3]+a[x-1] (x>3) 求a数列的第n项对1000000007(10^9+7)取余的值. 输入输出格式 输入格式: 第一行一 ...

  5. 51nod 1532 带可选字符的多字符串匹配(位运算)

    题意: 有一个文本串,它的长度为m (1 <= m <= 2000000),现在想找出其中所有的符合特定模式的子串位置.符合特定模式是指,该子串的长度为n (1 <= n <= ...

  6. Day20-初识Ajax

    想要实现的功能:点击提交以后,让数据发到后台进行验证,但是页面不刷新.悄悄提交用Ajax. 那么返回的字符串怎么样展示到前端HTML页面呢?可以在HTML中写个标签,定义一个选择器. 利用$('#id ...

  7. Splay 的区间操作

    学完Splay的查找作用,发现和普通的二叉查找树没什么区别,只是用了splay操作节省了时间开支. 而Splay序列之王的称号可不是白给的. Splay真正强大的地方是他的区间操作. 怎么实现呢? 我 ...

  8. Linux内核分析第三周——构造一个简单的Linux系统MenuOS

    构造一个简单的Linux系统MenuOS 李雪琦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/UST ...

  9. Codeforces 744C. Hongcow Buys a Deck of Cards(状压DP)

    这题的难点在于状态的设计 首先显然是个状压,需要一维表示卡的状态,另一维如果设计成天数,难以知道当前的钱数,没法确定是否能够购买新的卡,如果设计成钱数,会发现状态数过多,空间与时间都无法承受.但是可以 ...

  10. 在github fork的项目中推送与抓取

    github -- fork提交项目:自己的仓库和原仓库进行Git同步的操作. 1. 获取你fork的原仓库的更新过的最新代码:如果没有远程原始分支则需要增加. git remote add upst ...