事件层次分析 和 手势.scrollView.control跟响应链的关系 讲解


值得注意的事,当一个view上面有多个手势时,touch是无序的



1事件是怎么样产生与传递的?(由上至下的过程)

当发生一个触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中.

UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理.

主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件

触摸事件的传递是从父控件传递到子控件的.

如果一个父控件不能接收事件,那么它里面的了子控件也不能够接收事件.


如何寻找最合适的View?

主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件.

那如何找到最合适的View呢?

1.先判断自己是否能够接收触摸事件,如果能再继续往下判断,

2.再判断触摸的当前点在不在自己的身上.

3.如果在自己身上,它会从后往前遍历子控件,遍历出每一个子控件后,重复前面的两个步骤.

4.如果没有符合条件的子控件,那么它自己就是最适合的View.




2-事件响应(由下至上)

用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件,

找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理

那这些touches方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理

什么是响应者链条?

是由多个响应者对象连接起来的链条.

什么是响应者对象?

继承了UIResponder对象我们称之为响应者对象,也就是能处理事件的对象

事件传递的完整过程?

在产生一个事件时,系统会将该事件加入到一个由UIApplication管理的事件队列中,

UIApplication会从事件队列中取出最前面的事件,将它传递给先发送事件给应用程序的主窗口.

主窗口会调用hitTest方法寻找最适合的视图控件,找到后就会调用视图控件的touches方法来做具体的事情.

当调用touches方法,它的默认做法, 就会将事件顺着响应者链条往上传递,

传递给上一个响应者,接着就会调用上一个响应者的touches方法

如何去寻找上一个响应者?

1.如果当前的View是控制器的View,那么控制器就是上一个响应者.

2.如果当前的View不是控制器的View,那么它的父控件就是上一个响应者.

3.在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理

4.如果window对象也不处理,则其将事件或消息传递给UIApplication对象

5.如果UIApplication也不能处理该事件或消息,则将其丢弃

3事件的产生与传递(由上至下)    和    事件响应(由下至上)

(通俗讲: 方便大家理解,并无其他意思,勿喷.)

第一步  事件的传递与传递   (找到哪里发生地点)

一个地方发生了大型抢劫时间.

国家知道了有这个事件,就先省份上找,并按照省份名单顺序, 国家问浙江: 浙江浙江,是你那边的事吗?  浙江回答:不是.    再问福建:

福建,是你那边的事吗?  福建回答:是的.  然后福建在问它下面的城市,以此类推,一直找到 最终的地方.

第二步 事件响应 (把事件交给合适的部门处理)

发生的地点把 这个事件, 交给了这个地点的部门,   地点的部门说对不起,我处理不了.  

地点部门交给了  市部门,市部门收到事件,判断能否处理(能处理则不传递上去,不能则传给上级部门),以此类推,一直传到国家(如果连国家都不能处理则 丢弃这个事情)

补充

处理部门判断能否处理  的标准是  1手势的响应2继承于UIControl都会阻断 响应者链条的往上传递(button继承于UIControl)3继承于UIScroll也会阻断往上传递

即总的来说:能响应事件的对象通常默认取消事件往上传递(都有部门接收处理这个事件了,当然不用往上传了,哪怕像button scroll这种流氓部门 不管有对这个事件只接收不处理,它都打断了往上传递)










3-hitTest方法与PointInside方法

个人通俗理解
1顺序:先执行hitTest 再PointInside 

2 hitTest作用:寻找个合适的View,先遍历subView寻找,找到则返回view,找不到返回self

PointInside判断点击的点 是否在这个View上

作用:寻找最适合的View

参数:当前手指所在的点.产生的事件

返回值:返回谁, 谁就是最适合的View.

只要一个事件,传递给一个控件时, 就会调用这个控件的hitTest方法

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

作用:判断point在不在方法调用者上

point:必须是方法调用者的坐标系

hitTest方法底层会调用这个方法,判断点在不在控件上.

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{

return YES;

}

hitTest底层实现:

1.判断当前能不能接收事件

if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01)

return  nil;

2.判断触摸点在不在当前的控件上

if(![self pointInside:point withEvent:event]) return nil;

3.从后往前遍历自己的子控件

int count = (int)self.subviews.count;

for (int i = count - 1; i >= 0;i-- ) {

UIView *childV = self.subviews[i];

把当前坐标系上的点转换成子控件坐标系上的点.

CGPoint childP = [self convertPoint:point toView:childV];

判断自己的子控件是不是最适合的View

UIView *fitView = [childV hitTest:childP withEvent:event];

如果子控件是最合适的View,直接返回

if (fitView) {

return  fitView;

}

}


(下图代码可以看出 hitTest里面 执行pointInSide)



4.自己就是最适合的View

return self.



4一个控件什么情况下不能够接收事件.

1.不接收用户交互时不能够处理事件

userInteractionEnabled = NO

2.当一个控件隐藏的时候不能够接收事件

Hidden = YES的时候

3.当一个控件为透明白时候也不能够接收事件

注意:UIImageView的userInteractionEnabled默认就是NO,

因此UIImageView以及它的子控件默认是不能接收触摸事件的

5响应链的实际运用

1放大button的作用域(超过它的frame仍能响应)

2点击的view,却让其他view响应


6-hitTest练习1

业务逻辑:

底部一个按钮, 按钮的上面有一个View,遮挡在按钮的上面.

点击View时, View接收事件,当发现点击的点在按钮的位置时, 让底部的按钮处理事件.

实现思路:

实现View的touchBegain方法,先坚听UIView的点击.

并去实现UIView的HitTest方法, 在hitTest方法当中通过把当前点转换成按钮所在的坐标系

CGPoint btnP = [self convertPoint:point toView:self.btn];

转换过后查看当前点在不在按钮上,如果在按钮上,就直接返回按钮.

如果有在按钮上,保持系统默认做法.

实现代码:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

判断当前点在不在按钮上.

把当前点转换成按钮所在的坐标系

CGPoint btnP = [self convertPoint:point toView:self.btn];

if ([self.btn pointInside:btnP withEvent:event]) {

return self.btn;

}else{

return [super hitTest:point withEvent:event];

}

}


7-hitTest方法练习2

业务逻辑:

按钮可以随着手指拖动而拖动.拖动过程当中,按钮当中的子控件也跟着拖动.

让超过按钮的子控件也能够响应事件,一般情况下,当一个控件超过他的父控件的时候,是不能够接收事件的.

现在要做的事情就让超过父控件的按钮也能够响应事件.

实现思路:

先办到让按钮能够跟随着手指移动而移动.

实现按钮的touchesMoved方法,在touchesMoved方法当中,获得当前手指所在的点.以前上一个点.

分别计算X轴的偏移量以及Y轴的偏移量.

然后修改当前按钮的transform让按钮办到能够跟随着手指移动而移动.

第二步, 实现按钮的hitTest方法.

在该方法当中去判断当前的点在不在按钮的子控件上.

如果在按钮的子控件上.就返回按钮的子控件如果不在的话, 就保持系统的默认做法.

实现代码:

第一步,让按钮能够跟随着手指移动而移动

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{

获取当前的手指

UITouch *touch = [touches anyObject];

获取当前手指所在的点

CGPoint curP = [touch locationInView:self];

获取当前手指的上一个点

CGPoint preP = [touch previousLocationInView:self];

计算X轴的偏移量

CGFloat offsetX = curP.x - preP.x;

计算Y轴的偏移量

CGFloat offsetY = curP.y - preP.y;

修改按钮的形变,让按钮能够移动.

self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);

}

第二步,实现hitTest方法,判断手指当前所在的点在不在按钮的子控件上.

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

把当前所在的点转换成按钮子控件上面的点

CGPoint chatP =  [self convertPoint:point toView:self.chatBtn];

判断转换后的点在不在按钮的控件上.

if ([self.chatBtn pointInside:chatP withEvent:event]) {如果在

直接返回,也就意味着,当前最适合的View,就是这个按钮

return self.chatBtn;

}else{如果不在,那么就保持系统原有做法.

return  [super hitTest:point withEvent:event];

}

}



相关知识扩充



关于手势

值得注意的是:手势识别器 是 先于touch方法(touch响应链!)   捕捉到touch object!!!

cancelsTouchesInView/delaysTouchesBegan/delaysTouchesEnded

(0)首先要知道的是

1.这3个属性是作用于GestureRecognizers(手势识别)与触摸事件之间联系的属性。实际应用中好像很少会把它们放到一起,大多都只是运用手势识别,所以这3个属性应该很少会用到。

2.对于触摸事件,window只会有一个控件来接收touch。这个控件是首先接触到touch的并且重写了触摸事件方法(一个即可)的控件

3.手势识别和触摸事件是两个独立的事,只是可以通过这3个属性互相影响,不要混淆。

4手势是view外部来添加  ,  touch是view内部处理,两个是分开,且手势优先级比touch高

(1)在默认情况下(即这3个属性都处于默认值的情况下)(这些属性是 手势对它自己的view!!!)

如果触摸window,首先由window上最先符合条件的控件(该控件记为hit-test

view)接收到该touch并触发触摸事件touchesBegan。同时如果某个控件的手势识别器接收到了该touch,就会进行识别。手势识别成功之后发送触摸事件touchesCancelled给hit-testview,hit-test

view不再响应touch。(即打断 往上传递的响应链条)

(2)cancelsTouchesInView:(默认yes)

默认为YES,这种情况下当手势识别器识别到touch之后,会发送touchesCancelled给hit-testview以取消hit-test view对touch的响应,这个时候只有手势识别器响应touch。

当设置成NO时,手势识别器识别到touch之后不会发送touchesCancelled给hit-test,这个时候手势识别器和hit-test view均响应touch。

(3)delaysTouchesBegan:(默认no)

默认是NO,这种情况下当发生一个touch时,手势识别器先捕捉到到touch,然后发给hit-testview,两者各自做出响应。

如果设置为YES,手势识别器在识别的过程中(注意是识别过程),不会将touch发给hit-test

view,即hit-testview不会有任何触摸事件。!!只有在识别失败之后才会将touch发给hit-testview,这种情况下hit-test

view的响应会延迟约0.15ms。

(4)delaysTouchesEnded:(默认yes)

默认为YES。这种情况下发生一个touch时,在手势识别成功后,发送给touchesCancelled消息给hit-testview,手势识别失败时,会延迟大概0.15ms,期间没有接收到别的touch才会发送touchesEnded。如果设置为NO,则不会延迟,即会立即发送touchesEnded以结束当前触摸。

(5)手势共存 与 排斥(以下是手势对手势!!)

1:[tapGesture  requireGestureRecognizerToFail:swipeGesture]

swipe判断失败后  才判断tap

2:(代理方法)-

(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer

*)otherGestureRecognizer

这里返回YES,代表跟别的手势共存;如果返回NO,不一定代表不共存(可能另一个手势返回yes就可以共存,只要两个手势任一返回yes就可以)

3:-

(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer

*)otherGestureRecognizer

另外一个手势识别fail的时候,才会识别自己

4-

(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer

*)otherGestureRecognizer

我被另外一个手势变成Fail

(6)button 是用这个方法发送时间(补充)

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event






关于scrollView

有很多新闻类的App顶部都有一个滑动菜单栏,主要模型可能是由一个UIScrollView包含多个UIButton控件组成;当你操作的时候,手指如果是很迅速的在上面划过,会发现即使手指触摸的地方有UIButton,但是并没有触发该UIButton的任何触摸事件,这就是上面提到的case1;当你手指是缓慢划过或根本就没动,才会触发UIButton的触摸事件,这是case2的情况。

(0):scrollView默认封装了两个手势 pan和pinch

(1):scrollView的touch事件传递的两个重要的属性delaysContentTouches和canCancelContentTouches

delaysContentTouches(默认yes)(这个属性也是上面说scrollView默认阻断 响应链的原因)

delay延迟对subView的touch响应,肯定会优先响应UIScrollview滑动事件

delaysContentTouches。默认值为YES;如果设置为NO,则无论手指移动的多么快,始终都会将触摸事件传递给内部控件;设置为NO可能会影响到UIScrollView的滚动功能。


canCancelContentTouches(默认yes)

注意:如果scrollView(内部)- (BOOL)touchesShouldCancelInContentView:(UIView *)view
也设置,那两个要配合,平时就不要写这个方法

如果属性值为YES并且跟踪到手指正触摸到一个内容控件,这时如果用户拖动手指的距离足够产生滚动,那么内容控件将收到一个touchesCancelled:withEvent:消息,而scroll

view将这次触摸作为滚动来处理。如果值为NO,一旦content

view开始跟踪(tracking==YES),则无论手指是否移动,scrollView都不会滚动。

简单通俗点说,如果为YES,就会等待用户下一步动作,如果用户移动手指到一定距离,就会把这个操作作为滚动来处理并开始滚动,同时发送一个touchesCancelled:withEvent:消息给内容控件,由控件自行处理。如果为NO,就不会等待用户下一步动作,并始终不会触发scrollView的滚动了。




关于control

简单区分UIResponder与UIControl

UIResponder类:上承NSObject,下接UIView ,UIVIewController ,UIApplacation;响应点,压,滑;

UIControl类:上承UIView,下接UIButton等开关按钮;


主要区别在于:

前者,主要是响应某个动作,执行某个行为--

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event;

后者,在继承了前者的属性基础上,还能够相应某个动作,为某个对象,添加动作--

- (void) addTarget:(id)target action:(SEL)action forControlEvents(UIControlEvents)controlEvents

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event

返回值:YES 接受用户通过addTarget:action:forControlEvents添加的事件继续处理。

返回值:NO  则屏蔽用户添加的任何事件

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event可以用这个追踪 button上touch的改变  (有点类似于viewControl里面的touchMoved)

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 如果用户重写了该方法,则不会执行由用户添加的其他事件,直接屏蔽了用户的事件



分派事件

使用下面两个方法分派事件给响应者处理:

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event;

-

(void)sendActionsForControlEvents:(UIControlEvents)controlEvents;

// send all actions associated with events

事件与操作的区别:事件报告对屏幕的触摸;操作报告对控件的操纵。



手势共存 互斥案例




最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容