iOS的应用中,用户与App进行交互,会产生很多事件,这些事件是如何产生,响应的链条又是怎样传递的呢,下面将会一一解答以上的问题。
一. 事件传递&响应流程
事件传递&响应详情图:
event-process.png-
IOKit.framework
为系统内核的库 -
SpringBoard.app
相当于手机的桌面 -
Source1
主要接收系统的消息 -
Source0
-UIApplication
-UIWindow
-
从window开始系统会调用
hitTest:withEvent:
和pointInside
来找到最优响应者,具体过程可参考下图:
-
比如我们在
self.view
上依次添加view1
、view2
、view3
(3个view是同级关系),那么系统用-hitTest:withEvent:
以及-pointInside:withEvent:
时会先从view3
开始便利,如果-pointInside:withEvent:
返回YES
就继续遍历view3
的subviews
(如果view3
没有子视图,那么会返回view3
),如果-pointInside:withEvent:
返回NO
就开始遍历view2
。反序遍历,最后一个添加的subview
开始。也算是一种算法优化。 -
UITouch
会给gestureRecognizers
和最优响应者也就是hitTestView
发送消息 -
默认
view
会走其touchBegan:withEvent:
等方法,当gestureRecognizers
找到识别的gestureRecognizer后,将会独自占有该touch,即会调用其他gestureRecognizer
和hitTestView
的touchCancelled:withEvent:
方法,并且它们不再收到该touche事件,也就不会走响应链流程。下面会具体阐述UIContol
和UIScrollView
和其子类与手势之间的冲突和关系。 -
当该事件响应完毕,主线程的
Runloop
开始睡眠,等待下一个事件。
二. 事件传递
2.1 hitTest:withEvent:和pointInside:withEvent:
示例图:
event-test示例代码:
LMGrayView *grayView = [[LMGrayView alloc] initWithFrame:CGRectMake(100, 200, kScreenWidth/2, 400)];
grayView.backgroundColor = [UIColor grayColor];
[self.view addSubview:grayView];
LMRedView *redView = [[LMRedView alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3)];
redView.backgroundColor = [UIColor redColor];
[grayView addSubview:redView];
LMBlueView *blueView = [[LMBlueView alloc] initWithFrame:CGRectMake(grayView.bounds.size.width/2, grayView.bounds.size.height * 2/3, grayView.bounds.size.width/2, grayView.bounds.size.height/3)];
blueView.backgroundColor = [UIColor blueColor];
// blueView.userInteractionEnabled = NO;
// blueView.hidden = YES;
// blueView.alpha = 0.1;
[grayView addSubview:blueView];
LMYellowView *yellowView = [[LMYellowView alloc] initWithFrame:CGRectMake(CGRectGetMinX(grayView.frame), CGRectGetMaxY(grayView.frame) + 20, grayView.bounds.size.width, 100)];
yellowView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:yellowView];
LMGrayView
中的代码:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"method: %s", __PRETTY_FUNCTION__);
return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"method: %s", __PRETTY_FUNCTION__);
return [super pointInside:point withEvent:event];
}
点击redView
,打印日志:
2019-12-30 14:13:37.206432+0800 Event-Demo[39601:999578] method: -[LMYellowView hitTest:withEvent:]
2019-12-30 14:13:37.206691+0800 Event-Demo[39601:999578] method: -[LMYellowView pointInside:withEvent:]
2019-12-30 14:13:37.206888+0800 Event-Demo[39601:999578] method: -[LMGrayView hitTest:withEvent:]
2019-12-30 14:13:37.207052+0800 Event-Demo[39601:999578] method: -[LMGrayView pointInside:withEvent:]
2019-12-30 14:13:37.207243+0800 Event-Demo[39601:999578] method: -[LMBlueView hitTest:withEvent:]
2019-12-30 14:13:37.207425+0800 Event-Demo[39601:999578] method: -[LMBlueView pointInside:withEvent:]
2019-12-30 14:13:37.207581+0800 Event-Demo[39601:999578] method: -[LMRedView hitTest:withEvent:]
2019-12-30 14:13:37.207730+0800 Event-Demo[39601:999578] method: -[LMRedView pointInside:withEvent:]
2019-12-30 14:13:37.208041+0800 Event-Demo[39601:999578] method: -[LMYellowView hitTest:withEvent:]
2019-12-30 14:13:37.208186+0800 Event-Demo[39601:999578] method: -[LMYellowView pointInside:withEvent:]
2019-12-30 14:13:37.208336+0800 Event-Demo[39601:999578] method: -[LMGrayView hitTest:withEvent:]
2019-12-30 14:13:37.208502+0800 Event-Demo[39601:999578] method: -[LMGrayView pointInside:withEvent:]
2019-12-30 14:13:37.208863+0800 Event-Demo[39601:999578] method: -[LMBlueView hitTest:withEvent:]
2019-12-30 14:13:37.209127+0800 Event-Demo[39601:999578] method: -[LMBlueView pointInside:withEvent:]
2019-12-30 14:13:37.209373+0800 Event-Demo[39601:999578] method: -[LMRedView hitTest:withEvent:]
2019-12-30 14:13:37.225252+0800 Event-Demo[39601:999578] method: -[LMRedView pointInside:withEvent:]
经测试可得知:
-
-hitTest:withEvent:
方法内部会调用-pointInside:withEvent:
方法。 - 一次点击会触发两次
-hitTest:withEvent:
方法。 -
-pointInside:withEvent:
方法会判断触摸点是否在当前视图范围内。 -
-hitTest:withEvent:
方法根据-pointInside:withEvent:
的返回值决定是否返回当前视图。 - 调用顺序是,如果跨级,就从父视图逐级向其子视图遍历调用;如果同级,就按照同级子视图添加到其父视图上的顺序,从后向前遍历,即后添加的先遍历,先添加的后遍历的
FILO
模式。 - 如果父视图的
-pointInside:withEvent:
方法返回NO
,就不会遍历其子视图;如果返回YES
,就会遍历其子视图,即调用其子视图的-hitTest:withEvent:
方法。 - 如果一个子视图的
-hitTest:withEvent:
方法返回值不为nil
,那么就停止遍历,不会继续调用尚未遍历过的同级子视图的-hitTest:withEvent:
方法。 -
-hitTest:withEvent:
方法返回的视图决定最后响应事件的对象。
总结:在默认情况下,即不重写上述两个方法的情况下,当点击屏幕时,会从最底层的父视图开始向上逐级遍历,直到最顶层的子视图。如果发现触摸点在当前视图范围内,就会遍历当前视图的子视图,检测触点是否在其子视图范围内,如果在子视图范围内就检测该子视图的子视图,重复这个过程直到找到一个视图它没有子视图满足触摸点在其范围内,换句话说就是找到一个触点在其范围内的最顶层的视图。
2.2 hitTest:withEvent:内部实现代码还原
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"-----%@",self.nextResponder.class);
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) return nil;
//判断点在不在这个视图里
if ([self pointInside:point withEvent:event]) {
//在这个视图 遍历该视图的子视图
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
//转换坐标到子视图
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
//递归调用hitTest:withEvent继续判断
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
//在这里打印self.class可以看到递归返回的顺序。
return hitTestView;
}
}
//这里就是该视图没有子视图了 点在该视图中,所以直接返回本身,上面的hitTestView就是这个。
NSLog(@"选中的view:%@",self.class);
return self;
}
//不在这个视图直接返回nil
return nil;
}
2.3 hitTest:withEvent:应用实例
1、扩大Button的
点击区域
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(CGRectInset(self.bounds, -20, -20), point)) {
return YES;
}
return NO;
}
2、子view
超出了父view
的bounds
响应事件
第一种方法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL flag = NO;
for (UIView *view in self.subviews) {
if (CGRectContainsPoint(view.frame, point)){
flag = YES;
break;
}
}
return flag;
}
第二种方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
3、如果Button
被一个View
遮住,在触摸View
时,希望该Button
能够响应事件
in View.m里,第一种方法:
击View及View的非交互子View(例如UIImageView),则该Button可以响应事件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL next = YES;
for (UIView *view in self.subviews) {
if ([view isKindOfClass:[UIControl class]]) {
if (CGRectContainsPoint(view.frame, point)){
next = NO;
break;
}
}
}
return !next;
}
in View.m里,第二种方法:
点击View本身Button会响应该事件,点击View的任何一个子View,Button不会响应事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if (view == self) {
return nil;
}
return view;
}
4、ScrollView page
滑动
in scrollView.m,第一种方法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
if (CGRectContainsPoint(CGRectInset(self.bounds, -40, 0), point)) {
return YES;
}
return NO;
}
in scrollView.superView.m,第二种方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitTestView = [super hitTest:point withEvent:event];
if (hitTestView) {
hitTestView = self.scrollView;
}
return hitTestView;
}
三. 事件响应
3.1 响应链的组成
event-response之前示例代码,LMGrayView
中的代码:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"method: %s", __PRETTY_FUNCTION__);
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"method: %s", __PRETTY_FUNCTION__);
[super touchesEnded:touches withEvent:event];
}
点击redView
,打印日志:
2019-12-30 15:17:02.486239+0800 Event-Demo[39956:1042564] method: -[LMRedView touchesBegan:withEvent:]
2019-12-30 15:17:02.486401+0800 Event-Demo[39956:1042564] method: -[LMGrayView touchesBegan:withEvent:]
2019-12-30 15:17:02.486529+0800 Event-Demo[39956:1042564] method: -[ViewController touchesBegan:withEvent:]
2019-12-30 15:17:02.486969+0800 Event-Demo[39956:1042564] method: -[LMRedView touchesEnded:withEvent:]
2019-12-30 15:17:02.487088+0800 Event-Demo[39956:1042564] method: -[LMGrayView touchesEnded:withEvent:]
2019-12-30 15:17:02.487198+0800 Event-Demo[39956:1042564] method: -[ViewController touchesEnded:withEvent:]
因为只实现到controller
的touches
事件方法因此只打印到controller
。
经测试可得知:
-
响应链是通过
nextResponder
属性组成的一个链表。 -
在事件的响应中,如果某个控件实现了
touches...
方法,则这个事件将由该控件来接受,如果调用了[super touches….]
;就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….
方法。 -
如果当前view是控制器的view,那么控制器就上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图。
-
在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给
window对象
进行处理。 -
如果
window
对象也不处理,则其将事件或消息传递给UIApplication
对象。 -
如果
UIApplication
也不能处理该事件或消息,则将其丢弃。
3.2 UIControl与UIScrollView阻断响应链
将实例中的grayView
替换成button或scrollView
LMButton *button = [[LMButton alloc] initWithFrame:CGRectMake(100, 200, kScreenWidth/2, 400)];
button.backgroundColor = [UIColor grayColor];
[self.view addSubview:button];
// LMScrollView *scrollView = [[LMScrollView alloc] initWithFrame:CGRectMake(100, 200, kScreenWidth/2, 400)];
// scrollView.backgroundColor = [UIColor grayColor];
// [self.view addSubview:scrollView];
LMRedView *redView = [[LMRedView alloc] initWithFrame:CGRectMake(0, 0, button.bounds.size.width / 2, button.bounds.size.height / 3)];
redView.backgroundColor = [UIColor redColor];
[button addSubview:redView];
点击redView
,打印日志:
button时:
2019-12-30 15:43:52.429658+0800 Event-Demo[40130:1060351] method: -[LMRedView touchesBegan:withEvent:]
2019-12-30 15:43:52.429797+0800 Event-Demo[40130:1060351] method: -[LMButton touchesBegan:withEvent:]
2019-12-30 15:43:52.434574+0800 Event-Demo[40130:1060351] method: -[LMRedView touchesEnded:withEvent:]
2019-12-30 15:43:52.434771+0800 Event-Demo[40130:1060351] method: -[LMButton touchesEnded:withEvent:]
scrollView时:
2019-12-30 15:47:26.362262+0800 Event-Demo[40167:1063630] method: -[LMRedView touchesBegan:withEvent:]
2019-12-30 15:47:26.362456+0800 Event-Demo[40167:1063630] method: -[LMScrollView touchesBegan:withEvent:]
2019-12-30 15:47:26.362628+0800 Event-Demo[40167:1063630] method: -[LMRedView touchesEnded:withEvent:]
2019-12-30 15:47:26.362793+0800 Event-Demo[40167:1063630] method: -[LMScrollView touchesEnded:withEvent:]
经测试可得知:
-
UIControl
与UIScrollView
会阻断响应链,也就是说在响应UIControl
与UIScrollView
自身对touch的处理方式并不会调用nextResponder
对应的方法。 -
通过在
Button子类
中重写touches
的方法,发现如果不调用super
的touches
对应的方法则不会响应点击事件。由此可以大致推断出UIControl其子类
响应点击原理大致为:根据添加target:action:
时设置的UIControlEvents
,在touches
的合适方法调用target的action
方法。 -
通过重写
tableView子类
的touches
方法,发现如果不调用super
的touches
对应的方法则不会走tableview:didSelectRowAtIndexPath:
方法。由此可以大致推断出UIScrollView
其子类是在其touches
方法中处理点击事件的。
四. 手势Gesture
4.1 手势Gesture与Touch事件的关系
将示例中的grayView/redView/blueView
添加手势:
LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
[grayView addGestureRecognizer:grayGesture];
LMRedGestureRecognizer *redGesture = [[LMRedGestureRecognizer alloc] initWithTarget:self action:@selector(redViewClick:)];
[redView addGestureRecognizer:redGesture];
LMBlueGestureRecognizer *blueGesture = [[LMBlueGestureRecognizer alloc] initWithTarget:self action:@selector(blueViewClick:)];
[blueView addGestureRecognizer:blueGesture];
点击redView
,打印日志:
2019-12-30 16:18:28.629710+0800 Event-Demo[40396:1087484] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
2019-12-30 16:18:28.629952+0800 Event-Demo[40396:1087484] method: -[LMRedGestureRecognizer touchesBegan:withEvent:]
2019-12-30 16:18:28.630483+0800 Event-Demo[40396:1087484] method: -[LMRedView touchesBegan:withEvent:]
2019-12-30 16:18:28.630679+0800 Event-Demo[40396:1087484] method: -[LMGrayView touchesBegan:withEvent:]
2019-12-30 16:18:28.630829+0800 Event-Demo[40396:1087484] method: -[ViewController touchesBegan:withEvent:]
2019-12-30 16:18:28.666787+0800 Event-Demo[40396:1087484] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
2019-12-30 16:18:28.667075+0800 Event-Demo[40396:1087484] method: -[LMRedGestureRecognizer touchesEnded:withEvent:]
2019-12-30 16:18:28.667517+0800 Event-Demo[40396:1087484] method: -[ViewController redViewClick:]
2019-12-30 16:18:28.667757+0800 Event-Demo[40396:1087484] method: -[LMRedView touchesCancelled:withEvent:]
2019-12-30 16:18:28.667937+0800 Event-Demo[40396:1087484] method: -[LMGrayView touchesCancelled:withEvent:]
2019-12-30 16:18:28.668081+0800 Event-Demo[40396:1087484] method: -[ViewController touchesCancelled:withEvent:]
经测试可得知:
-
当通过
hitTest
和pointInside
找到最优响应者后,会给gestureRecognizers
和相应的view同时发送touchBegin
消息,如果找到合适gestureRecognizer
则会独有该touches
,即调用view的touheCancel
消息,接着有gestreRecognizer
来响应事件。 -
cancelsTouchesInView
:默认为YES
。表示当手势识别成功后,取消最佳响应者对象对于事件的响应,并不再向最佳响应者发送事件。若设置为NO
,则表示在手势识别器识别成功后仍然向最佳响应者发送事件,最佳响应者仍响应事件。 -
delaysTouchesBegan
:默认为NO
,即在手势识别器识别手势期间,触摸对象状态发生变化时,都会发送给最佳响应者,若设置成YES
,则在识别手势期间,触摸状态发生变化时不会发送给最佳响应者。 -
delaysTouchesEnded
:默认为NO
。默认情况下当手势识别器未能识别手势时,若此时触摸已经结束,则会立即通知Application
发送状态为end的touch事件给最佳响应者以调用touchesEnded:withEvent:
结束事件响应;若设置为YES
,则会在手势识别失败时,延迟一小段时间(0.15s)再调用响应者的touchesEnded:withEvent:
。
4.2 手势Gesture和UIControl的关系
将示例中的grayView
添加手势,redView
替换成button
:
LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
[grayView addGestureRecognizer:grayGesture];
LMButton *button = [[LMButton alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3)];
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[grayView addSubview:button];
点击button
,打印日志:
2019-12-30 16:22:43.255640+0800 Event-Demo[40421:1091112] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
2019-12-30 16:22:43.256213+0800 Event-Demo[40421:1091112] method: -[LMButton touchesBegan:withEvent:]
2019-12-30 16:22:43.261559+0800 Event-Demo[40421:1091112] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
2019-12-30 16:22:43.261907+0800 Event-Demo[40421:1091112] method: -[LMButton touchesEnded:withEvent:]
2019-12-30 16:22:43.262199+0800 Event-Demo[40421:1091112] method: -[ViewController buttonClick:]
有手势的grayView
上增加一个button
,button
上增加一个blueView
:
LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
[grayView addGestureRecognizer:grayGesture];
LMButton *button = [[LMButton alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3)];
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[grayView addSubview:button];
LMBlueView *blueView = [[LMBlueView alloc] initWithFrame:CGRectMake(0, 0, button.bounds.size.width/2, button.bounds.size.height/2)];
blueView.backgroundColor = [UIColor blueColor];
[button addSubview:blueView];
点击blueView
,打印日志:
2019-12-30 16:28:05.892085+0800 Event-Demo[40457:1095228] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
2019-12-30 16:28:05.892806+0800 Event-Demo[40457:1095228] method: -[LMBlueView touchesBegan:withEvent:]
2019-12-30 16:28:05.899176+0800 Event-Demo[40457:1095228] method: -[LMButton touchesBegan:withEvent:]
2019-12-30 16:28:05.899547+0800 Event-Demo[40457:1095228] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
2019-12-30 16:28:05.899825+0800 Event-Demo[40457:1095228] method: -[ViewController grayViewClick:]
2019-12-30 16:28:05.899964+0800 Event-Demo[40457:1095228] method: -[LMBlueView touchesCancelled:withEvent:]
2019-12-30 16:28:05.900087+0800 Event-Demo[40457:1095228] method: -[LMButton touchesCancelled:withEvent:]
经测试最终得知:
-
UIControl
及其子类能够执行点击事件而不是走底层的手势的原因为:在识别到相应的gestureRecognizer
后如果当前的最优响应者是UIControl
及其子类并且当前的gestureRecognizer
不是UIContol
上的手势,则会响应UIControl
的target:action:
的方法。否则则会响应gestureRecognizer
的target:action:
的方法。
4.3 手势Gesture和UIScrollview的关系
将示例中的grayView
添加手势,redView
替换成tableView
:
LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
[grayView addGestureRecognizer:grayGesture];
LMTableView *tableView = [[LMTableView alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3) style:UITableViewStylePlain];
tableView.backgroundColor = [UIColor redColor];
tableView.dataSource = self;
tableView.delegate = self;
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:NSStringFromClass([UITableViewCell class])];
[grayView addSubview:tableView];
点击cell
,打印日志:
无手势:
2019-12-30 16:40:55.246500+0800 Event-Demo[40548:1105091] method: -[LMTableView touchesBegan:withEvent:]
2019-12-30 16:40:55.247270+0800 Event-Demo[40548:1105091] method: -[LMTableView touchesEnded:withEvent:]
2019-12-30 16:40:55.248467+0800 Event-Demo[40548:1105091] method: -[ViewController tableView:didSelectRowAtIndexPath:]
有手势:
2019-12-30 16:38:34.628235+0800 Event-Demo[40526:1103012] method: -[LMGrayView pointInside:withEvent:]
2019-12-30 16:38:34.629412+0800 Event-Demo[40526:1103012] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
2019-12-30 16:38:34.682632+0800 Event-Demo[40526:1103012] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
2019-12-30 16:38:34.683721+0800 Event-Demo[40526:1103012] method: -[ViewController grayViewClick:]
经测试最终得知:
-
UIScrollView
为最优响应者并且父控件没有手势时UIScrollView才
可以自己处理点击事件。否则被父控件的gestureRecognizer
占有。 - 当父控件有手势时
UIScrollView
的touches
方法都不执行,类似于设置delaysTouchesBegan
为YES
。 - 虽然
UIScrollView
及其子类和UIControl
及其子类类似都可以阻断响应链,但是当UIScrollView
及其子类为最优响应者时,如果父控件中有gestureRecognizer
依然会被其占有。
4.4 UIScrollView点击穿透解决方案
当UIScrollView
为最优响应者父控件有手势时,UIScrollView
及其子类的点击代理方法和touchesBegan
方法不响应。
解决的三种办法:
-
可以通过给父控件手势设置
cancelsTouchesInView
为NO
,则会同时响应gestureRecognizer
的事件和UIScrollView
及其子类的代理方法和touches
事件。 -
在父控件中的手势的代理方法里面做一下判断,当
touch.view
是我们需要触发的view的时候,return NO
,这样就不会走手势方法,而去触发这个touch.view
这个对象的方法了。- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { if ([NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"]){ return NO; } return YES; }
-
可以通过给
UIScrollView
及其子类添加gestureRecognizer
,从而来调用需要处理的事情。
本文首发于我的个人博客 https://limeng99.club/,转载请标明出处。
网友评论