iOS 事件响应
当用户产生一个事件,app就需要对这个事件进行响应。一个事件会经历一条特定的路径直到传递到能够处理它的对象处。一开始,UIApplication的单例将头部的事件从事件队列里面取出来,分发给能够处理它的对象,一般是keywindow。如果是触摸类的事件,那么keywindow首先会尝试将事件传递给触摸发生的视图(可以通过hit-test来找到触摸发生的视图)。如果是动作或者远程控制类的事件,keywindow会传递给first responder进行处理。
所有的事件路径,它的终极目标就是为了找到一个能处理并响应该事件的对象。
hit-testing找到发生触摸事件的视图
看官网上的这张图,
1.触摸发生在视图A内,那么就检查A的子视图B和C。
2.触摸发生在子视图C内,不在子视图B内。那么就去检查视图C的子视图D和E,不再去检查B的子视图。
3.触摸发生在视图E中,不在视图D中,且E没有子视图是包含这个触摸点的,那么E就是我们要找的触摸发生的视图。
此处有一个要注意的,用3D的视角来看下面这个图,B是A的子视图,B有超出A区域之外的灰色部分。如果触摸发生在灰色部分,(视图默认的clipsToBounds属性是NO,表示子视图超过的部分不会被裁剪),那么B收不到触摸事件。因为触摸没有发生在A的区域内,所以就不会去检查A的子视图是否能响应触摸。
用代码举个例子
// ViewController.m
#import "ViewController.h"
#import "FatherView.h"
#import "SonView.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
FatherView * fatherView = [[FatherView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)];
fatherView.backgroundColor = [UIColor orangeColor];
SonView * sonView = [[SonView alloc] initWithFrame:CGRectMake(10, 10, 200, 200)];
sonView.backgroundColor = [UIColor redColor];
[fatherView setNeedsDisplay];
[sonView setNeedsDisplay];
[fatherView addSubview:sonView];
[self.view addSubview:fatherView];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"viewcontroller touchesBegin");
}
// FatherView.m
#import "FatherView.h"
@implementation FatherView
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"fatherview touchesBegin");
}
@end
// SonView.m
@implementation SonView
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"SonView touchesBegin");
}
@end
这个时候,你在子视图即红色视图超过橘色视图的部分触摸一下,打印出来如下。即,SonView和FatherView都没有响应,而是ViewController进行了响应。(照惯性思维,触摸是发生在红色视图上的,应该由红色视图响应,实际上不是。)
如果父视图设置了clipsToBounds = YES
,那么它的子视图超过的部分就会被裁减掉。例如下面的代码,效果是这样的。
FatherView * fatherView = [[FatherView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)];
fatherView.backgroundColor = [UIColor orangeColor];
fatherView.clipsToBounds = YES;
SonView * sonView = [[SonView alloc] initWithFrame:CGRectMake(10, 10, 200, 200)];
sonView.backgroundColor = [UIColor redColor];
[fatherView setNeedsDisplay];
[sonView setNeedsDisplay];
[fatherView addSubview:sonView];
[self.view addSubview:fatherView];
http://www.jianshu.com/p/c5fee92ddf31
http://smnh.me/hit-testing-in-ios/
responder chain 响应链
能响应事件的对象叫做responder object,UIResponder
是这些对象的基类, UIApplication
、 UIViewController
、UIView
都是可以响应事件的。
注意
响应链除了响应触摸和动作事件之外,还响应其他东西,下图是从官网上截下来的响应链能处理的所有东西。
即其他还有远程控制事件、动作消息(比如按下一个按钮,但按钮的target是nil,那么消息就在响应链上传递)、编辑消息(比如剪切、拷贝、黏贴)、编辑textField或者textView。
特定的响应路径
看一下官网上的图,响应链是这样的。如果initialView没有响应,那么就会层层往父级传递,直至最后传递到UIApplication,如果UIApplication也不能处理它,那么这个消息就被忽略。
userInteractionEnable
当一个view设置userInteractionEnable为NO的时候,它就不会响应用户交互相关的动作了。
那么如果父视图设置了userInteractionEnable=NO,子视图还会接收到触摸吗?
UIResponder
看一下这个类里面的相关方法。
nextResponder
,即在响应链中找到下一个响应对象。
isFirstResponder
是否是第一响应者
canBecomeFirstResponder
, becomeFirstResponder
,canResignFirstResponder
,resignFirstResponder
看名字就知道了。
inputView
只有在UITextField或者UITextView的时候才是可赋值的,其他情况下都只是read-only只读。inputViewController
,inputAccessoryView
,inputAccessoryViewController
同理。
reloadInputViews
,当成为first responder的时候更新inputView和accessoryView
响应触摸事件
最后一个方法touchesEstimatedPropertiesUpdated:
是iOS 9.1新加上去的方法。以后再研究。
响应Motion事件
还有其他相关方法,不一一列举了。
参考
4.事件--官方文档