内容简介:目录前言众所周知、手势识别器的权限比响应链更高。
目录
-
前言
-
TouchEvent 响应链
-
UIControl
-
UIGestureRecognizer
-
结论
前言
众所周知、手势识别器的权限比响应链更高。
而UIControl的响应机制不涉及响应链、由UIAPPlication直接分发。
于是、会出现一些冲突的问题。
比如给self.view添加手势之后、cell不可点击、但UIButton不受影响。
可以看看 《iOS点击事件和手势冲突》 。
而在网上找了找、但总觉得不那么直观。
TouchEvent 响应链
点击一个View
frame #20: 0x000000010ce0e4fd NSObject`-[View1 touchesBegan:withEvent:](self=0x00007f9c19e20a10, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x000060400010d410) at View1.m:107 frame #21: 0x000000010ec87e1a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052 frame #22: 0x000000010ec897c1 UIKit`-[UIWindow sendEvent:] + 4086 frame #23: 0x000000010ec2d310 UIKit`-[UIApplication sendEvent:] + 352
-
结论
-
响应链由UIWindow的_sendTouchesForEvent方法调起。
-
而_sendTouchesForEvent由sendEvent:方法调起。
UIControl
点击一个UIButton
-
UIButton响应线
frame #0: 0x000000010ce0d2a7 NSObject`-[ViewController btnClick](self=0x00007f9c19f0f080, _cmd="btnClick") at ViewController.m:55 frame #1: 0x000000010ec133e8 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 83 frame #2: 0x000000010ed8e7a4 UIKit`-[UIControl sendAction:to:forEvent:] + 67 frame #3: 0x000000010ed8eac1 UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 450 frame #4: 0x000000010ed8da09 UIKit`-[UIControl touchesEnded:withEvent:] + 580 frame #5: 0x000000010ec880bf UIKit`-[UIWindow _sendTouchesForEvent:] + 2729 frame #6: 0x000000010ec897c1 UIKit`-[UIWindow sendEvent:] + 4086 frame #7: 0x000000010ec2d310 UIKit`-[UIApplication sendEvent:] + 352
-
响应链开始线
frame #0: 0x000000010c9db48b NSObject`-[View1 touchesBegan:withEvent:](self=0x00007f8a15405cc0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000001044a0) at View1.m:109 frame #1: 0x000000010e854e1a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052 frame #2: 0x000000010e8567c1 UIKit`-[UIWindow sendEvent:] + 4086 frame #3: 0x000000010e7fa310 UIKit`-[UIApplication sendEvent:] + 352
-
响应链结束线
frame #0: 0x000000010c9db60b NSObject`-[View1 touchesEnded:withEvent:](self=0x00007f8a15405cc0, _cmd="touchesEnded:withEvent:", touches=1 element, event=0x00006000001044a0) at View1.m:120 frame #1: 0x000000010e8550bf UIKit`-[UIWindow _sendTouchesForEvent:] + 2729 frame #2: 0x000000010e8567c1 UIKit`-[UIWindow sendEvent:] + 4086 frame #3: 0x000000010e7fa310 UIKit`-[UIApplication sendEvent:] + 352
-
结论
-
会由UIWindow的_sendTouchesForEvent:调起响应链。
-
在touchesBegan:withEvent判断如果为UIControl、则截断响应链(不向next reponder传递)、并且尝试识别。
-
识别成功则直接由UIApplication向target发送Action事件。
UIGestureRecognizer
点击一个添加了手势的View或者SubView
-
手势响应线
frame #0: 0x00000001034ea447 NSObject`-[ViewController doTapChange](self=0x00007fbb31607910, _cmd="doTapChange") at ViewController.m:101 frame #1: 0x00000001058e754f UIKit`-[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 57 frame #2: 0x00000001058f0324 UIKit`_UIGestureRecognizerSendTargetActions + 109 frame #3: 0x00000001058edb6c UIKit`_UIGestureRecognizerSendActions + 307 frame #4: 0x00000001058ecdc0 UIKit`-[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 859 frame #5: 0x00000001058d1e24 UIKit`_UIGestureEnvironmentUpdate + 1329 frame #6: 0x00000001058d18a7 UIKit`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 484 frame #7: 0x00000001058d09a9 UIKit`-[UIGestureEnvironment _updateGesturesForEvent:window:] + 281 frame #8: 0x00000001053667ab UIKit`-[UIWindow sendEvent:] + 4064 frame #9: 0x000000010530a310 UIKit`-[UIApplication sendEvent:] + 352
-
响应链开始线
[ViewController touchesBegan:withEvent:](self=0x00007f8a15426010, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000001044a0) at ViewController.m:97 frame #1: 0x000000010ea0c767 UIKit`forwardTouchMethod + 340 frame #2: 0x000000010ea0c602 UIKit`-[UIResponder touchesBegan:withEvent:] + 49 frame #3: 0x000000010e854e1a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052 frame #4: 0x000000010e8567c1 UIKit`-[UIWindow sendEvent:] + 4086 frame #5: 0x000000010e7fa310 UIKit`-[UIApplication sendEvent:] + 352
-
响应链取消线
frame #0: 0x00000001034eb5ed NSObject`-[View1 touchesCancelled:withEvent:](self=0x00007fbb314061e0, _cmd="touchesCancelled:withEvent:", touches=0x000060400025a1f0, event=0x000060000011d0a0) at View1.m:120 frame #1: 0x0000000105303434 UIKit`__98-[UIApplication _cancelViewProcessingOfTouches:withEvent:sendingTouchesCancelledToViewsOfTouches:]_block_invoke + 663 frame #2: 0x0000000105302c22 UIKit`-[UIApplication _cancelTouches:withEvent:includingGestures:notificationBlock:] + 1091 frame #3: 0x0000000105303167 UIKit`-[UIApplication _cancelViewProcessingOfTouches:withEvent:sendingTouchesCancelledToViewsOfTouches:] + 158 frame #4: 0x00000001058d41e8 UIKit`_UIGestureEnvironmentCancelTouches + 687 frame #5: 0x00000001058d3f26 UIKit`-[UIGestureEnvironment _cancelTouches:event:] + 42 frame #6: 0x00000001058ed2d0 UIKit`-[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 2155 frame #7: 0x00000001058d1e24 UIKit`_UIGestureEnvironmentUpdate + 1329 frame #8: 0x00000001058d18a7 UIKit`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 484 frame #9: 0x00000001058d09a9 UIKit`-[UIGestureEnvironment _updateGesturesForEvent:window:] + 281 frame #10: 0x00000001053667ab UIKit`-[UIWindow sendEvent:] + 4064 frame #11: 0x000000010530a310 UIKit`-[UIApplication sendEvent:] + 352
-
[KTUITapGestureRecognizer touchesBegan:withEvent:]线:
-[KTUITapGestureRecognizer touchesBegan:withEvent:](self=0x00006000003d6700, _cmd="touchesBegan:withEvent:", 0x0000600000dd1d40) ITapGestureRecognizer.m:14 -[UIGestureRecognizer _touchesBegan:withEvent:] + 240 -[UITouchesEvent _sendEventToGestureRecognizer:] + 287 -[UIGestureEnvironment _updateForEvent:window:]_block_invoke + 64 -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 276 -[UIGestureEnvironment _updateForEvent:window:] + 200 -[UIWindow sendEvent:] + 4058 -[UIApplication sendEvent:] + 352
-
然后来科普一下手势的识别过程
Window在将事件传递给hit-tested view之前,会先将事件传递给相关的手势识别器并由手势识别器优先识别。若手势识别器成功识别了事件,就会取消hit-tested view对事件的响应;若手势识别器没能识别事件,hit-tested view才完全接手事件的响应权。
关于谁先谁后。佐证的话、你可以自定义一个子类并且重载一些方法、这里直接贴结果:
大致理解是,Window在将事件传递给hit-tested view之前,会先将事件传递给相关的手势识别器并由手势识别器优先识别。若手势识别器成功识别了事件,就会取消hit-tested view对事件的响应;若手势识别器没能识别事件,hit-tested view才完全接手事件的响应权。
佐证的话、你可以自定义一个子类并且重载一些方法、这里直接贴结果
先用一个离散型手势做实验:
14:20:17-[KTUITapGestureRecognizer touchesBegan:withEvent:] 14:20:17-[KTUITapGestureRecognizer2 touchesBegan:withEvent:] 14:20:17-[View2 touchesBegan:withEvent:] 14:20:17-[View touchesBegan:withEvent:] 14:20:18-[KTUITapGestureRecognizer touchesEnded:withEvent:] 14:20:18-[KTUITapGestureRecognizer2 touchesEnded:withEvent:] 14:20:18-[View2 tapAction] 14:20:18-[View2 touchesCancelled:withEvent:] 14:20:18-[View touchesCancelled:withEvent:]
而对于持续型手势
在一开始滑动的过程中,手势识别器处在识别手势阶段,滑动产生的连续事件既会传递给手势识别器又会传递给View,因此View的 touchesMoved:withEvent:在开始一段时间内会持续调用;
当手势识别器成功识别了该滑动手势时,手势识别器的action开始调用,同时通知Application取消View对事件的响应。之后仅由滑动手势识别器接收事件并响应,View不再接收事件。
这里有几个点可以说说:
-
可以看到右侧有一秒的时间差
也就是说View的后续动作会等待GestureRecognizer的识别结果。
-
KTUITapGestureRecognizer以及KTUITapGestureRecognizer2的方法都被触发了
也就是说是hit-tested列表中所有View上的手势识别器都会得到机会去识别事件。(依赖UITouch中的gestureRecognizers属性)
-
至于最后触发谁、取决于代理中的设置。默认按照响应链的顺序。
并且手势在触摸事件处理流程中,处于观察者的角色,其不是view层级结构的一部分,所以也不参与或者依赖responder chain(你把上层View的touch不调用super也影响不了)。
其流程大概如下图所示:
注:图中view与手势的关系是,手势关联在view或view的superview(可能多级)上。
-
结论
-
手势的Action由UIGestureRecognizer直接调用。
-
响应链的开始依旧由UIWindow的_sendTouchesForEvent触发。
-
响应链的cancel由UIGestureEnvironment发起
-
手势的识别(KTUITapGestureRecognizer的touchesBeagn)并不依赖响应链(View的touchesBeagn)、而是由[UIWindow sendEvent:]方法中被UIGestureEnvironment直接调起。
结论
有几个地方可以提一下:
-
响应链本身什么都不做
所以手势以及UIControl的处理本身并不依赖响应链。以下是勘误:
对于UIView是这样。但当我看过UIControl后有了新的理解~
但对于UIControl。如果注册了Action、那么UIContrl便会在自己的位置上截断响应链、并且TouchBegan等方法内部处理TouchDown等等状态以及对应的Action。
此外需要注意的是:只要注册了Action、就会截断响应链(但并不一定会触发-sendAction:to:forEvent:)。哪怕是向一个UIButton注册EditingDidEnd。
由于UIControl依赖touchBegan并且具有截断的特性、所以位于下方的另一个UIButton (如果你没
搞什么特殊的事情)才永远不会被触发。
-
响应链必然会触发、只是后期处理不一样。
如果是手势、则被取消。如果是UIControl则被结束。
如果设置tap.delaysTouchesBegan = YES;在识别结果出来之前则不会触发View的touchesBegan。
-
关于事件处理的优先级。
这个光看堆栈应该是看不出来的。
但结合一些结论、可以试着猜测:
-
目标View上有手势
手势优先识别
-
目标View上没有手势但是归属系统UIControl
UIControl优先处理
-
目标View就是个普通View(或者自定义的Control)
下层手势优先识别、如果没有手势/识别失败则交给响应链
-
如何将事件发送给指定的View/手势
全部依赖UITouch的属性
1.Window
@property(nullable,nonatomic,readonly,strong) UIWindow *window;
2.响应链/UIControl
@property(nullable,nonatomic,readonly,strong) UIView *view;
3.手势
@property(nullable,nonatomic,readonly,copy) NSArray <uigesturerecognizer nbsp=""> *gestureRecognizers NS_AVAILABLE_IOS(3_2); </uigesturerecognizer>
最后
本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果愿意补充以及不吝赐教小弟会更加感激。
作者:kirito_song
链接:https://www.jianshu.com/p/56c20736a73c
以上所述就是小编给大家介绍的《iOS基础补完计划--透过堆栈看事件响应机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ajax修炼之道
(美)哥特兰、高伯瑞斯、艾米亚 / 徐锋,胡冰 / 电子工业出版社 / 2006-4 / 29.8
Ajax将静态Web页面转变为充满交互的应用。现在您不需要牺牲Web应用程序部署的简单性,就可以将“胖”客户端应用程序部署到客户端。不过对于很多人业说,Ajax看起来很难。这就是我们撰写本书的原因。作为实践的指导,本书揭开了Ajax神秘的面纱,教您如何以简单的方式使用Ajax。本书内容覆盖了DHTML、Javascript和闻名已久的XmlHttp Request回调技术的基础知识。您将了解如何将......一起来看看 《Ajax修炼之道》 这本书的介绍吧!