iOS事件传递

本篇主要讲解iOS事件传递的整个过程,大部分内容翻译自Apple Developer Guide,原文链接

当一个用户事件产生的时候,UIKit 会创建一个事件对象来描述这个用户事件。然后它会将该事件对象放进UIApplication对象所维护的事件队列中。对于触摸事件而言,产生的事件对象便是一个包含UIEvent的集合(NSSet)对象。

一个事件会朝着特定的路径进行传递直到遇到一个可以处理它的对象为止。首先,UIApplication对象会从事件队列的栈顶拿到事件,并且通过分发下去的方式处理该事件。一般情况下,UIApplication发送该事件到app的主窗口对象(key window object),主窗口对象会根据事件类型(Touch event、Motion and remote control events)选择一个相应的对象,将事件传递给它。

  • Touch events. 窗口对象首先会尝试将事件传递给事件发生所在的view对象。这个view对象我们称之为 hit-test view。寻找这个hit-test view的过程叫做hit-testing(定位Touch事件发生在哪个view上)。
  • Motion and remote control events. 就这些事件而言,窗口对象会将 shaking-motion 或者 remote control event发送给响应者链条的第一个响应者(the first responder)去处理。

事件沿着响应者链条向上传递,其最终目标是发现一个能够响应和处理该事件的对象。因此,UIKit首先将事件传递给一个最适合处理它的对象。就Touch events来说,该对象就是hit-test view,对于其它事件而言,该对象是第一个响应者。下面的章节介绍hit-test view和第一响应者是怎么确定的。

通过hit-test找到触摸的View

当用户触摸屏幕的时候,iOS会使用 hit-testing去找到用户触摸的view对象。hit-testing机制会在所有相关view中检查触摸是否发生在哪一个view的范围内中,如果找到了,那么通过递归的方式继续检查这个view的子view们。当递归结束的时候,处于view层级结构中最下面的那个包含触摸点的view就是 hit-test view。在iOS确定了 hit-test view之后,会将触摸事件传递给它寻求处理。

图2-1

举个例子,假设用户触摸了图2-1中的 view E。iOS通过下面的步骤来检查subviews,最终确定hit-test view:

  1. 触摸点在 view A里面,所以检查它的子控件 B 和 C。
  2. 触摸点不在 view B里面,但是在 view C里面,所以检查它的子控件 D 和 E。
  3. 触摸点不在 view D里面,但是在 view E里面。view E是包含该触摸点的最底层 view,所以它就是 hit-test view.

传递一个CGPoint 和 UIEvent对象给UIView 的 hitTest:withEvent: 方法,它会返回 hit-test view对象。hitTest:withEvent: 方法开始的时候会调用自身的 pointInside:withEvent: 方法。如果传递给 hitTest:withEvent: 的点在view对象的范围内,pointInside:withEvent: 会返回YES。如果返回YES,那么父view的 hitTest:withEvent: 会继续通过递归的方式调用子view的 hitTest:withEvent 方法。

如果传递给hitTest:withEvent:的点不在根view的范围内,那么调用 pointInside:withEvent: 会返回NO,这个点就被忽略,然后 hitTest:withEvent: 会返回nil。如果一个子view返回NO,那么从该子view到它的所有子view的递归分支会被忽略,因为如果一个触摸点不在子view的范围内,那么同样的,这个触摸点不会在该子view的所有子view范围内。这意味着,当触摸的点所在的范围是在父view之外,那么子view是无法接收到触摸事件的,因为事件接收的充要条件是触摸点必须同时处在父view和子view的范围内。这种情况发生在子view的大小超过了父view的大小并且子view的clipsToBounds属性值为NO,并且触摸点超过父view的范围,但是位于子view的范围内的时候。

注意:触摸事件对象的整个生命周期都会和它的 hit-test view关联,即使触摸最后超出了这个hit-test view的范围

hit-test view作为第一个可能处理touch event的对象,如果它不能处理这个事件,那么事件会沿着响应者链条往上传递给上一个响应者,直到系统找到一个可以处理这个事件的对象为止。

响应者链条是由响应者对象构成的

许多类型的事件都依赖响应者链条进行事件传递。响应者链条是一连串连接在一起的响应者对象。它从第一响应者开始到UIApplication对象结束。如果第一响应者不能处理事件,系统会将事件传递给响应者链条的下一个响应者。

响应者对象是一个能够响应和处理事件的对象。它们有一个共同点就是都是继承自UIResponder,UIResponder的程序接口不仅定义了事件处理方式,还有响应者的一些共同行为。UIApplication,UIViewController和UIView都是响应者,这意味着UIView与其所有子类和大多数主要控制器对象都是响应者,注意 Core Animation layers 不是响应者。

第一响应者被设计作为第一个接收事件的对象,一般而言,第一响应者是一个view对象。一个对象通过下面的两个步骤成为第一响应者:

  1. 覆盖 canBecomeFirstResponder方法并且返回YES。
  2. 接收到一个 becomeFirstResponder 消息,如果有需要,一个对象可以给自己发送这个消息。

注意:在让一个对象成为第一响应者之前,确保你的app已经渲染好它的视图,举个例子,我们一般在viewDidAppear:方法中调用becomeFirstResponder方法,如果我们尝试在viewWillAppear: 方法中调用,由于我们的视图还没有渲染到屏幕上,所以 becomeFirstResponder 方法会返回NO。

事件并不是唯一一种依赖响应者链条进行传递的对象。响应者链条被用在下面的情况中:

  • Touch events. 如果 hit-test view不能处理触摸事件,那么事件会顺着响应者链条从 hit-test view 开始传递上去。
  • Motion events. 为了让UIKit处理 shake-motion 事件,第一响应者必须覆盖UIRespnder的 motionBegan:withEvent: 或者 motionEnded:withEvent: 方法。
  • Remote control events. 为了处理 remote control 事件,第一响应者必须实现UIResponder的 remoteControlReceivedWithEvent: 方法
  • Action messages. 当用户对一个UIControl对象进行操作的时候,比如一个button或者switch,如果没有为该对象指定target和action method,那么一个Action message方法会从第一响应者开始顺着响应者链传递出去,这个第一响应者可以是这个UIControl对象本身。
  • Editing-menu messages.当用户使用了编辑菜单的一个指令选项的时候,iOS使用响应者链条去找一个实现了对应必要方法(例如cut:,copy: 和 paste: )的对象。
  • Text editing. 当用户触摸一个text field 或者是 text view的时候,这个view自动成为第一响应者。默认的,这个时候虚拟键盘会出现并且text field或text view会变成编辑状态。如果需要,你可以使用一个自定义的输入视图代替键盘。同样的,你可以为任何响应者对象添加一个自定义的输入视图(通过重定义inputView属性为readwrite并且赋值)。

在触摸text field和text view的时候,UIKit自动设置它们为第一响应者;如果想让其它UIResponder成为第一响应者,我们必须在代码中设置调用该对象的 becomeFirstRespnder 方法。

响应者链条的事件传递路径是遵循特定的规则形成的

如果最初的对象不是hit-test view或者第一响应者没有处理事件,UIKit会将事件传递给链条中的下一个响应者。每一个响应者都可以决定是去处理这个事件,还是通过调用 nextResponder 方法将事件传递给下一个响应者。这个过程会持续进行下去,直到找到一个能处理事件的响应者或者到UIApplication对象结束。

在iOS检测到一个事件并且将它传递给初始对象(一般是一个view)的时候,响应者链条开始有序地运作。初始view是第一个可能处理响应事件的对象。图2-2展示在两种界面视图结构下的两条事件传递路径。一个app的事件传递路径依赖于它的视图结构,不同的视图结构可能有不同的事件传递路径,但是所有事件传递路径都是有迹可循的。

图2-2

左边的app,它的事件传递过程如下:

  1. initial view 尝试去处理事件或者消息。如果它不能处理事件,它将事件传递给它的父控件,因为initial view 不是它所在控制器的直接视图,所以是将事件往上抛给父控件而不是控制器对象。
  2. 它的父控件尝试去处理事件。如果父控件不能处理事件,继续将事件传递给它的父控件,因为它也不是控制器的直接视图。
  3. 控制器的直接视图(controller.view)是所在控制器视图中层级最高的,如果这个视图还是无法处理事件,那么它会将事件抛给控制器。
  4. 控制器尝试去处理事件,如果不能处理事件,它会将事件抛给window对象。
  5. 如果window对象无法处理事件,window将事件抛给UIApplication对象。
  6. 如果UIApplication对象无法处理事件,那么就销毁这个事件。

右边的app,它的事件传递过程明显和第左边的不同,但是所有事件传递都有如下规则:

  1. 一个view顺着控制器视图的层级结构传递事件,直到到达控制器的直接视图,也就是最顶层的视图。
  2. 顶层视图将事件传递给它所在的控制器。
  3. 控制器传递事件给顶层视图的父控件。
    步骤 1-3 重复直到到达根控制器。
  4. 根控制器将事件抛给window对象。
  5. window对象将事件抛给UIApplication对象。

注意:当你在处理remote control events,action messages, shake-motion events 或者 editing-menu mesages的时候,如果想通过响应者链条传递事件,不要直接调用nextResponder,而是通过调用父类实现好事件处理方法,让UIKit去做事件传递。

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

推荐阅读更多精彩内容

  • 用户以多种方式操纵他们的iOS设备,例如触摸屏幕或摇动设备。 iOS会解释用户何时以及如何操作硬件并将此信息传递到...
    坤坤同学阅读 3,981评论 7 19
  • 本次笔记主要是整理一下关于 iOS 中关于事件传递和响应机制,参考了一些其他资料加上自己的理解。 事件 Event...
    varlarzh阅读 325评论 0 2
  • 事件传递:响应者链 当你设计一个app的时候,你很可能需要你的app能够动态响应某些事件。比如,触摸可以发生在屏幕...
    hjfrun阅读 1,025评论 1 5
  • 好奇触摸事件是如何从屏幕转移到APP内的?困惑于Cell怎么突然不能点击了?纠结于如何实现这个奇葩响应需求?亦或是...
    Lotheve阅读 56,806评论 51 597
  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 5,995评论 4 26