iOS触摸事件

事件的生命周期

1、系统响应阶段:触摸屏幕→IOKit(IOHIDEvent)→通过mach port(IPC进程间通信)转发给SpringBoard(IOHIDEvent)→SpringBoard通过当前桌面状态判断前台运行的app,并通过mach port转发给当前app

2、应用内部:当前app主线程RunLoop的Source1(监听mach port消息,接收SpringBoard事件)触发,并在Source0回调内部把IOHIDEvent封装为UIEvent,调用UIApplication的sendEvent把UIEvent传给UIWindow,开始通过hitTest:view寻找最佳响应者(UIResponder),找到最佳响应者后,事件开始在响应链中传递,最终被某个响应者/手势识别器/Target-Action模式捕捉并消耗掉,或没有找到任何响应对象后释放。

注:Mach Port进程端口,各进程利用它进行通信。

注:SpringBoard是一个系统进程,即桌面系统,统一管理和分发触摸事件。


传递链

事件的传递和分发,其实是寻找最佳响应者的过程(Hit-Testing 命中检测)。事件的传递起源于触摸状态的变化,一个点击会触发两次事件的传递(begin到end,touch的状态发生变化)

核心方法

1、- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {} // 视图是否能够响应事件

2、- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {} // 触摸点是否在hitTest方法返回的视图内

传递过程:UIApplication

→调用UIWindow的hitTest(多个window询问后显示的window(所以新添加的window记得makeKeyAndVisible))

→递归询问子视图是否能响应事件(实现:判断3种无法响应的情况→判断触摸点(return nil),从后往前遍历子视图(i = count - 1),convertPoint坐标转换到子视图上,并递归调用hitTest询问子视图中的子视图,如果有合适的则return,没有则return self)

→Window的hitTest方法返回最佳响应者,告知UIApplication。

注:无法响应的3种情况userInteractionEnabled = NO;hidden = YES;alpha < 0.01

事件拦截:定制视图,自定义事件流向。如遇到超出TabBar坐标范围的TabButton时,重写TabBar的pointInside方法,将坐标convert到Button上,再调pointInside判断是否在Button上即可实现越界点击。


响应链

事件首先传递给最佳响应者(hit-tested view)响应,并在响应链中的传递。

响应过程

UIApplication通过Event中的Touch对象找到触摸所属的Window,将事件通过sendEvent传递给Window,Window也通过sendEvent将事件优先传递给最佳响应者(直接传递,因为此时Window已找到最佳响应者并保存了起来),然后通过nextResponder方法获取下一个响应者,形成响应链。

注:UIView的nextResponder是VC(如果此View是根视图)或其父视图;

VC的nextResponder是Window(如果此VC是根视图),如果VC是被present出来的,则是它的presenting view controller;

UIWindow的nextResponder是UIApplication;

UIApplication的nextResponder是AppDelegate。

事件的响应

UIResponder对象通过4个touch方法响应触摸事件,但默认不做事情,只单纯的继续传递,重写方法可以截获事件,进行操作。(如创建自定义视图,重写touchMoved方法,通过touches和event属性实现简单的视图拖动)

注:在寻找最佳响应者时,所属的window和view会绑定到touch对象上,以供事件的传递过程中找到视图,给响应者发送事件。

事件的拦截

拦截和响应都是通过touchBegan方法控制的,默认实现为将事件沿着响应链继续向下传递。

1、不拦截,默认继续传递。

2、拦截,不再传递:重写touchesBegan处理事件,且不调用父类的方法。

3、拦截,继续传递:重写处理并调用父类的touchesBegan。

注:UIScrollViewDelayedTouchesBeganGestureRecognizer属性和手势延迟0.15秒的属性类似,所以,当视图中有手势和TableView共存时1、轻点手势只会执行手势而事件不会到达最佳响应者;2、短按会超过0.15到达最佳响应者但会因为手势拦截事件而被cancel;3、长按会因为手势识别失败,事件传递给最佳响应者执行cell的selected。


超级总结

1、UIWindow是分发事件和响应事件的重要角色,是事件的起点:寻找最佳响应者时从Window开始寻找子视图,找到后也是由Window通过sendEvent:将事件传递给最佳响应者(hit-tested view)。

2、响应链中,Control上没有Gesture优先响应Control,Responder优先级最低,甚至会被视图层级低于自己的Gesture打断并Cancel掉Touch状态。


UIEvent

事件本身,type标识事件类型(触摸,加速计等),allTouchs属性包含了如多个手指产生的所有触摸对象(UITouch)的集合。

UITouch

源起触摸,封装在UIEvent内部,在事件传递时用于判断hitTest-view和确定GestureRecognizers。1手指1触摸生成1个UITouch;N手指1触摸生成N个UITouch对象;N手指N触摸,通过触摸位置判断是更新上次的还是再生成一个UITouch。手指离开屏幕一段时间后,确定UITouch不再更新才会释放。

UIResponder

响应者对象,具备响应事件的能力,因为其提供了4个处理触摸事件的Touch方法:Began、Moved、Ended、Cancelled,在接收到事件时调用,可以做出响应。如UIView/UIViewController/UIApplication/AppDelegate。

UIControl

以Target-Action模式处理触摸事件,如UIButton、UISwitch。UIControl跟踪到触摸事件时会向Target发送事件以执行Action(只接收单点触控)。跟踪的4个方法:beginTrackingWithTouch:(UITouch *)touch、continueTracking、endTracking、cancelTracking。UIControl继承UIView,也具备普通UIResponder的身份,也有touch的4个方法,但默认实现与本类不同,如touchesBegan方法内部会调用beginTracking。

响应过程:先通过addTarget:action:forControlEvents:(UIControlEvents有许多关于交互事件的枚举)添加处理事件的target和action;当UIControl监听到事件时,sendAction把target、action和event都发给Application,再通过sendAction:from:to:forEvent向target发action(如果target为空,Application会在响应链中自己找能响应aciton的对象)


UIGestureRecognizer

同样具有响应事件的能力,分为离散型(tap、swipe)和持续型。4个处理事件的方法跟UIResponder一样但无关(在UIGestureRecognizerSubclass中声明)。

手势识别成功后的处理:事件响应在Source0回调的_UIApplicationHandleEventQueue()方法中,如果识别成了UIGesture手势时,首先调用Cancel打断当前的touch系列回调,然后将UIGestureRecognizer标记为待处理。当监听到BeforeWait事件时,回调函数_UIGestureRecognizerUpdateObserver()内部会获取所有标记待处理的手势,并执行手势回调。每当UIGestureRecognizer变化(创建销毁状态改变)时,RecognizerUpdate回调都会进行相应的处理。

离散型手势对响应链的影响

UIApplication在把事件传递给最佳响应者之前,会将事件先传递给相关的手势识别器开始识别,如果成功识别则会取消hit-tested view对事件的响应。但最佳响应者的touchBegan会先于手势的action执行,因为手势的识别成功(status = .recognized)的时机是稍晚的。但重写手势的touchBegan方法即可证明UIWindow先把事件传递给了手势。-[TapGestureRecognizer touchesBegan:withEvent:]-[View touchesBegan:withEvent:]-[TapGestureRecognizer touchesEnded:withEvent:]Gesture Taped Action-[View touchesCancelled:withEvent:]注:如何优先传递给手势识别器?在hit-test过程中收集了UIGestureRecognizer的数组,保存在了event绑定的touch上。

持续型手势对响应链的影响

一个滑动的交互:pan手势识别过程中,连续事件会先传递给最佳响应者持续调用touchesMoved;pan手势识别成功后执行action,并通知Application去cancel掉响应者对事件的响应,之后都由手势接收事件并响应。若没有识别成功,则会一直传递给最佳响应者。

UIGestureRecognizer的三个属性轻点、短按、长按

cancelsTouchesInView:决定手势识别成功后,是否cancel响应链中响应者的响应(拦截并阻断),默认是YES

delaysTouchesBegan:是否在手势识别期间,就阻断事件传递给最佳响应者,默认是NO

delaysTouchesEnded:手势识别失败且触摸结束时,是否延迟0.15秒通知最佳响应者调用touchesEnded以结束事件响应,默认是YES


相关问题

描述一下触摸事件的生命周期

分为系统响应阶段,和应用内部的传递、响应:IOKit到SpringBoard到APP(通过MachPort),应用内主线程RunLoop的Source回调中处理成UIEvent调UIApplication的sendEvent开始分发,通过hitTest找最佳响应者(传递链);响应者/手势识别器/Target-Action响应事件消耗或释放(响应链)。


系统是如何寻找最佳响应者的(传递链),如何在传递链中拦截事件

UIWindow中递归调用hitTest,判断三个交互条件+pointInside,满足则继续遍历子视图,否则返回自身。

重写pointInside可以扩大热区,拦截事件,自定义流向,最好不要调hitTest,因为实现不明,注意调super)


描述事件的响应过程(响应链),如何在响应链中拦截事件

事件由UIWindow先发送给最佳响应者,通过重写4个touch方法,截获处理后就终止(是否不再继续传递的标志是-是否调用super.touch方法),否则就nextResponder,期间手势识别器识别成功后会break掉touch的响应链。


UIGestureRecognizer对响应链的影响(离散型和持续型)

手势有识别过程,三个属性分别决定:识别成功后是否阻断响应链,是否开始识别时就阻断响应链,识别失败时候延迟通知最佳响应者调touchEnd。

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