iOS学习笔记系列 - 事件系统

在iOS中,事件(Events)是APP接受用户输入的一种方式。在iOS开发中比较重要的事件包括点击事件(Touch Events),运动事件(Motion Events)以及远程控制事件(Remote Control Events)。然而,在一篇文章里把它们一个个都详尽的讨论完并不是一件理智的事,苹果也有官方的说明,所以我们今天的主角只是其中最重要的点击事件,并以此为例,一起来讨论一下iOS的事件系统。

目录:

  • 响应器 (UIResponder)
  • iOS事件的生命周期
  • hitTest
  • 响应链(Responder Chain)
  • 总结

响应器 (UIResponder)

在iOS中,用来处理事件的抽象类是UIResponder,它包含了处理事件所需的常用方法,比如我们比较熟悉的

  • - (BOOL)canBecomeFirstResponder
  • - (void)becomeFirstResponder
  • - (BOOL)canResignFirstResponder
  • - (void)resignFirstResponder

等等。它是整个iOS事件系统的核心,UIView, UIViewController甚至UIApplication都是它的子类。可以说,响应器这个类是iOS事件系统的一个规范和协议,除非有特殊需要,开发者都应该按照这个协议处理事件,这样才能保证其他人看你的代码时不至于懵圈。

iOS事件的生命周期

iOS接收事件是从UIApplication开始的。熟悉Runloop的朋友应该知道,在主队列上运行的Runloop其中的一个步骤便是检测事件。当Runloop检测到事件时,便会依照一个称为hitTest的过程将事件转移到相应的类去进行处理。通过hitTest找到对本次事件负责的UIResponder类实例之后,事件便会通过Responder Chain依次传送,直至被某个UIResponder截断或者传回UIApplication。一个事件的生命周期大概就是这样一个过程。下面我们来分别详细讨论一下生命周期的两个阶段:hitTestResponder Chain 事件处理

hitTest

hitTest的主要目的是确定哪个UIResponder应该对事件负责。当UIApplication接收到一个单击事件时,它会调用UIWindow的hitTest:withEvent:方法确定负责的UIResponder实例。而确定是否对事件负责的标准就是看点击的点是否在它的frame范围内,这个可以通过调用pointInside:withEvent:来检测。如果确定了这个点在UIWindow的frame里面,它将会依次递归地调用它的rootview和subviews等的hitTest:withEvent方法,直到找到最前面的能处理这个事件的UIResponder,然后返回。

看个例子:(这里借用苹果官方的一张图解释一下)

from: Apple Documentation

如图,假设这样一个结构。当用户点击D的中心时,A首先接收到'hitTest:withEvent:'消息。由于点击的点在A的范围内,A变回依次问B和C。由于点不在B的范围内,B便会返回nil,于是A继续给C发hitTest:withEvent:消息。因为C包含了点击的点,于是C给D发hitTest:withEvent:的消息。这时候由于D没有subview,于是直接返回D自己。这样D就成了这个时间的第一响应人(firstResponder)。

这里要注意两点:

  • 这是一个深度优先的遍历过程(DFS Traversal);
  • 当A收到hitTest:withEvent:的消息时,先给B还是C发的顺序,笔者并没有找到相关的官方文档对此说明。但测试的结果显示,是按照- (NSArray *)subviews返回结果的逆顺序
hitTest讨论
  • UIViewclipsToBounds设置为NO时,有可能出现以下的布局:(B是A的子视图)

这是如果点击的是B超出A的位置,那么当A收到hitTest:withEvent:信息时,由于该点不在A的frame内,A便会返回nil了,而不会再向B发送消息询问。这样即使用户想点击的是B,B也无法接收到该事件。

当然,如果一定要对此进行处理,也可以用重载视图A的hitTest:withEvent:方法,让其给子视图B发送消息,从而实现B能收到相应事件的需求。

  • 当需要截获处理某一事件,并且阻止subviews收到该事件时,可自行重载当前UIView中的hitTest:withEvent:方法,如下:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
  if (/* 判断事件event为要截获的事件 */) {
    return self;
  }
  
  return [super hitTest:point withEvent:event];
}

响应链(Responder Chain)

在hitTest步骤完成之后将会得到一个该事件的第一负责人,一般称之为该事件的hit-test view,由于一般都是一个UIView实例。然后,该hit-test view- (BOOL)canBecomeFirstResponder方法将会被调用。(注意:UIView默认返回值是NO,需要在子类中重载这个方法返回YES

如果hit-test view表示无法响应该事件,则该事件将会依照一个所谓的响应链(Responder Chain)依次往上层传递,直至有实例响应并处理该事件或一直回到UIApplication。确定传递对象的方法是调用自身的- (UIResponder *)nextResponder方法,开发者可以利用这个来debug。

系统默认的响应链构成如下:

  • hitTest返回的hit-test view将会在响应链的最前面;
  • hit-test view的superview将会是它的nextResponder;
  • 依次沿着superview的路径传递下去,知道遇到一个UIViewControllerview,然后它的nextResponder将会是这个UIViewController实例。
  • 以此类推,直到一个UIViewControllerUIWindow的根控制器,这样这个UIViewControllernextResponder将会是UIWindow
  • UIWindownextResponder就会是UIApplication
  • 最后的一个响应器是UIApplicationDelegate

当然,这些都是可以被修改的。但除非你非常确定需要修改,否则不要去更改这些默认的设定,因为那样很容易让其他人看不懂你的代码。

总结

了解这些流程可以让你在开发过程中少踩很多坑。很多iOS新手开发最容易碰到的棘手问题就是不知道为什么我的touch事件明明加上去了却并没有得到相应,希望看了这篇文章之后,你就可以自己去分析为什么了。其实我写这篇文章的本意是还想总结一些常见的错误,但不知不觉啰里啰嗦也写了这么长了,只有下一篇再进行总结了。读者如果对自己踩过的相关的坑记忆犹新的话,也可以在评论里进行回复。笔者将尽力在下一篇文章中进行详尽的分析,希望通过这种方式把经验分享给更多的人。

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

推荐阅读更多精彩内容