理解iOS Event Handling
写在前面
最近的一个iOS App项目中遇到了这么问题:通过App访问服务器的大多数资源不需要登录,但是访问某些资源是需要用户提供验证的,一般来说,通常App的做法(譬如美团App)将这些资源放在“我的”模块,在未登录情况下,当点击“我的”某个模块时,以modally给出一个界面用于登录,我不晓得美团是怎么实现的;但在我看来,比较优雅的方式是在网络底层实现“modally呈现登录界面”,具体来说,当用户欲访问“我的购物券”时,在网络访问层发现用户未登录,就弹出一个登陆界面,用户登录成功后,就将这个登录界面给dismiss掉。
这种实现的好处是使得登录模块管理比较集中,便于维护。其实现也并不难,modally方式呈现一个页面的代码很简单,如下:
|
1
2
|
UIViewController *VC = [][UIViewController alloc] init];
[self presentViewController:VC animated:YES completion:nil];
|
这对于在网络层处理有点麻烦,因为presentViewController:animated:completion:是UIViewController中定义的方法,所以,modally呈现一个页面的第一个任务是需要知道当前所呈现的view controller。搜索“iOS获取当前view controller”在这里貌似找到一个比较好的解决方案。所以最后我的关键代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
UIViewController *result = nil;
// 1. get current window
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
if (window.windowLevel != UIWindowLevelNormal) {
NSArray *windows = [[UIApplication sharedApplication] windows];
for(UIWindow * tmpWin in windows) {
if (tmpWin.windowLevel == UIWindowLevelNormal) {
window = tmpWin;
break;
}
}
}
// 2. get current View Controller
UIView *frontView = [[window subviews] objectAtIndex:0];
id nextResponder = [frontView nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
result = nextResponder;
} else {
result = window.rootViewController;
}
// 3. present Login VC modally
// 3.1 init VC
LoginNavigationController *nav = [[LoginNavigationController alloc] init];
// 3.2 show VC
[result presentViewController:nav animated:YES completion:nil];
|
从未对这段代码进行验证,今天得空决定对这段简单的代码进行简单的分析。之前对UIResponder并不熟悉,得补充补充相关知识点,笔者参考的文档是官方文档《Event Handling Guide for iOS》。
在iOS中,把“对事件的处理”称为responder。
Hit-Testing
iOS的views之间有各种覆盖关系,譬如view A是view B上的subview,那么如果在view A上产生了一个touch event,那么说“touch event在A上发生”还是“touch event在B上发生”呢?Hit-Testing就是为了解决这个问题而生的概念。
关于Hit-testing,官方文档是这么描述的:
iOS uses hit-testing to find the view that is under a touch. Hit-testing involves checking whether a touch is within the bounds of any relevant view objects. If it is, it recursively checks all of what view’s subviews. The lowest view in the view hierarchy that contains the touch point becomes the hit-test view. After iOS determines the hit-test view, it passes the touch event to that view for handling.
来用图文对此过程进行描述,如下图:

假设在view E上发生了一个user touch event,iOS的hit-test过程如下:
- touch event发生在view A的bounds内,继续检查subview B和subview C;
- touch event没有发生在view B的bounds内,但发生在view C的bounds内,检查view C的subview D和subview E;
- touch event没有发生在view D的bounds内,但发生在view E的bounds内,而view E之下再无subviews,所以确定view E是所谓的hit-test view。
回到上述的引文,笔者阅读这段文字时产生了一个疑问:如果view A确实是view B的subview,但是view A所对应的区域不在view B的bounds之内的,譬如下图,那么在view A上发生的touch event还能被检测到吗?

P.S:经过笔者的测验,得到的结果是不能!
在viewDidLoad方法中添加如下代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
self.view.backgroundColor = [UIColor whiteColor];
UIView *lightGrayView = ({
UIView *view = [[UIView alloc] init];
view.backgroundColor = UIColor.lightGrayColor;
view;
});
[self.view addSubview:lightGrayView];
[lightGrayView makeConstraints:^(MASConstraintMaker *make) {
UIView *superView = self.view;
int padding = 10.0;
make.left.equalTo(superView.left).offset(padding);
make.right.equalTo(superView.right).offset(-padding);
make.height.equalTo(lightGrayView.width).offset(-padding);
make.centerY.equalTo(superView.centerY).offset(0);
}];
UIView *yellowView = ({
UIView *view = [[UIView alloc] init];
view.backgroundColor = UIColor.yellowColor;
view;
});
[lightGrayView addSubview:yellowView];
[yellowView makeConstraints:^(MASConstraintMaker *make) {
UIView *superView = lightGrayView;
make.width.equalTo(superView.width).multipliedBy(0.5).offset(0.0);
make.height.equalTo(yellowView.width).offset(0.0);
make.left.equalTo(superView.left).offset(0);
make.top.equalTo(superView.top).offset(-80);
}];
yellowView.userInteractionEnabled = YES;
[yellowView addGestureRecognizer:({
UITapGestureRecognizer *gesture =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sayHello:)];
gesture.numberOfTapsRequired = 1;
gesture;
})];
|
P.S:代码中使用了第三方autolayout框架Masonry,结果如下图:

黄色view是灰色view的subview,但是黄色view并不全部包括在灰色view的bounds中,为黄色view添加一个tap gesture事件绑定,测试发现,当点击黄色view上半部分(其灰色bounds区域之外部分),tap event并未被触发,但是点击黄色view的下半部分,touch event会被触发。
故而,可以得出结论,如果view A是view B的subview,但view A并未在view B的bounds之内,则当view A的touch event发生在view B的bounds之外时,其响应其实并未发生(或说并不被承认)。
其实文档中也有相应的解释:
If the point passed into hitTest:withEvent: is not inside the bounds of the view, the first call to the pointInside:withEvent: method returns NO, the point is ignored, and hitTest:withEvent: returns nil. If a subview returns NO, that whole branch of the view hierarchy is ignored, because if the touch did not occur in that subview, it also did not occur in any of that subview’s subviews.This means that any point in a subview that is outside of its superview can’t receive touch events because the touch point has to be within the bounds of the superview and the subview.
找到Hit-test view又如何呢?hit-test view是第一个有机会对touch event进行处理的view;如果它不处理,则往上层走……这涉及responder chain这个概念。
The Responder Chain
responder chain在iOS中是一个非常重要的概念(相比在Android中也一样)。在了解responder chain这个概念之前,得先明白responder。文档如是说:
A responder object is an object that can responder to and handle events. The UIResponder class is the base class for all responder objects, and it defines the programmatic interface not only for event handling but also for common responder behavior.
UIApplication, UIViewController以及UIView这几个类的实例及其子类实例都是responders。
当iOS的hit-testing找到了发生的target view之后,event就在responder chain之间传送,第一个有机会处理event的responder当然是hit-testing view,如果它不能处理这个event,则会往上层(譬如super view)继续传送,但考虑到UIViewController和UIApplication的实例也是responder,所以可能传送路线不只是hit-testing view — super view — super view — super view — …那么简单,具体来说有两个的传递路线:
![QQ20150215-2]QQ20150215-2.png
为什么是两种呢?其实稍微看一下也很简单,第二种对应的是view controller嵌套的情况。
OK,回过头来看“获取当前view controller”的代码,第一个部分“get current window”没啥问题,第二部分代码(如下)就值得玩味了:
|
1
2
3
4
5
6
7
8
|
UIView *frontView = [[window subviews] objectAtIndex:0];
id nextResponder = [frontView nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
result = nextResponder;
} else {
result = window.rootViewController;
}
|
这段代码一般情况是没问题的,但根据不是特别好,因为这段代码执行的前提是key windows的subview只有一个,而不含其他的view,所以笔者稍作了一点修改:
|
1
2
3
4
5
6
7
8
9
10
11
|
// 2. get current View Controller
NSArray *subViews = [window subviews];
for (UIView *view in subViews) {
id nextResponder = [view nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
result = nextResponder;
}
}
if (result == nil) {
result = window.rootViewController;
}
|
OK,game over!关于responder,还有更多更“高端”的知识点,碰到了类似问题之后再说吧。
参考:
《Event Handling Guide for iOS》
理解iOS Event Handling的更多相关文章
- Event Handling Guide for iOS--(一)--About Events in iOS
About Events in iOS Users manipulate their iOS devices in a number of ways, such as touching the scr ...
- Event Handling Guide for iOS(五)
基本概念: 加速计: 又称加速度计,测量设备运动的加速度. 加速度: 矢量,描绘速度的方向和大小变化的快慢. 陀螺仪: 感测与维持方向的装置. 原文: Motion Event声明: 由于本人水平有限 ...
- Event Handling Guide for iOS--(三)---Event Delivery: The Responder Chain
Event Delivery: The Responder Chain 事件传递:响应链 When you design your app, it’s likely that you want to ...
- Event Handling Guide for iOS--(二)---Gesture Recognizers
Gesture Recognizers 手势识别器 Gesture recognizers convert low-level event handling code into higher-leve ...
- Event Handling in Spring
Spring内置的event有 1.ContextRefreshedEvent This event is published when the ApplicationContext is eithe ...
- 0112.1——iOS开发之理解iOS中的MVC设计模式
模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程 ...
- iOS开发之理解iOS中的MVC设计模式
模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程 ...
- 我所理解的event loop
灵魂三问 JS为什么是单线程的 我们都知道,JS是单线程的语言,那为什么呢?我的理解是JS设计之初就是为了在浏览器端完成DOM操作和一些简单交互的,既然涉及到DOM操作如果是多线程就会带来复杂的同步问 ...
- 通过实现一个TableView来理解iOS UI编程
推荐一篇神作: 通过实现一个TableView来理解iOS UI编程 http://blog.jobbole.com/61101/
随机推荐
- poj1149最大流经典构图神题
题意:n个顾客依次来买猪,有n个猪房,每个顾客每次可以开若干个房子,买完时,店主可以调整这位顾客 开的猪房里的猪,共m个猪房,每个猪房有若干猪,求最多能卖多少猪. 构图思想:顾客有先后,每个人想要的猪 ...
- Java游戏服务器搭建
一.前言 此游戏服务器架构是一个单服的形式,也就是说所有游戏逻辑在一个工程里,没有区分登陆服务器.战斗服务器.世界服务器等.此架构已成功应用在了多款页游服务器 .在此框架中没有实现相关业务逻辑,只有简 ...
- 使用icomoon把svg图片生成字体图标
今天看了使用icomoon来将svg转换成图标字体,本来是不会使用别人给的svg,也不清楚具体的好处是什么,查了svg以后,越来越懵,svg挺好的为什么要转成图标字体呢. 一.SVG介绍 SVG 是一 ...
- T1229 数字游戏 codevs
http://codevs.cn/problem/1229/ 题目描述 Description Lele 最近上课的时候都很无聊,所以他发明了一个数字游戏来打发时间. 这个游戏是这样的,首先,他拿出 ...
- 洛谷—— P1605 迷宫
P1605 迷宫 题目背景 迷宫 [问题描述] 给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过.给定起点坐标和 终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案.在 ...
- python type()函数
我怎么把一个变量的类型写入文件?a = 3type(a)貌似返回的是type类型,不能打印,也不能用文件的write怎么半,或者怎么转换成srt之类的? type()函数得到的是一个类型而不是字符串, ...
- qq空间微博等更多社交平台分享
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...
- [vxlan] 二 什么是VXLAN
VXLAN是一种mac in UDP的技术.简单讲就是传统的二层帧被封装到了UDP的package中.通过UDP的IP网络发送到目的地然后再解封装. VXLAN 跟VLAN对比,最重要的一个概念就是V ...
- ETCD 单机安装
由于测试的需要,有时需要搭建一个单机版的etcd 环境,为了方便以后搭建查看,现在对单机部署进行记录. 一.部署单机etcd 下载 指定版本的etcd下载地址 ftp://ftp.pbone.net/ ...
- Go -- 今日头条架构
夏绪宏,今日头条架构师,专注对高性能大规模 Web 架构,云计算.性能优化.编程语言理论等方向,PHP committer,HHVM 项目贡献者.2009 加入百度,先后从事大规模 IDC 自运维设施 ...