iOS里的触摸 UIResponder . UIGestureRecognizer
一:UIResponder :
触摸事件由触屏生成后如何传递到当前应用?
1510019-62b6b1eec26730aa.png1,触摸响应核心方法,寻找 hit-tested view:
- —(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
底层具体实现如下 :
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
// 把当前控件上的坐标系转换成子控件上的坐标系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 寻找到最合适的view
return fitView;
}
}
// 循环结束,表示没有比自己更合适的view
return self;
}
— (Bool)pointInside:(CGPoint)point withEvent:(UIEvent *)event
2, UIApplication将事件通过 sendEvent:
传递给事件所属的window,window同样通过 sendEvent:
再将事件传递给最佳响应者 hit-tested view,流程如下:
UIApplication ——> UIWindow ——> hit-tested view
3,响应链:
每个响应者必定都是UIResponder对象
UIResponder对象:
- UIView
- UIViewController
- UIApplication
- AppDelegate
响应四个方法:
//手指触碰屏幕,触摸开始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指在屏幕上移动
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指离开屏幕,触摸结束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸结束前,某个系统事件中断了触摸,例如电话呼入
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
通过4个响应触摸事件的方法来响应事件;,每个UIResponder对象默认都已经实现了这4个方法,但是默认不对事件做任何处理,单纯只是将事件沿着响应链传递。若要截获事件进行自定义的响应操作,就要重写相关的方法。
UIView *a_subA = [[UIView alloc]initWithFrame:CGRectMake(10, 10, 50, 50)];
a_subA.backgroundColor = UIColor.blueColor;
[a addSubview:a_subA];
上面只是单纯的添加view,无其他手势,只要在对应View里重写下方法该类的对象都可以让其相应对应触摸事件
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"YH_AViewtouchesEnded");
[super touchesEnded:touches withEvent:event];
}
Hit-Testing过程中的事件拦截自定义举例:
1, 不完整的事件,无法向下传递
2,子视图超过父视图,点击子视图超出父视图部分响应;
3,点A视图让B视图响应
……...
二:UIGestureRecognizer 手势识别器
问题1,初级:单独给AView 添加一个UITapGestureRecognizer 手势,Aview 还会调用那些自己的touch哪些方法?为什么会这样?
看测试demo结果:
- [AView touchesBegan:withEvent:]
- [AView testGesture]
- [AView touchesCancelled:withEvent:]
会调用 [AView touchesBegan]、[AView touchesCancelled]方法;当touchesCancelled调用后, 就只有手势能接受了;
注意点:
手势识别器的tapAction的调用时机 ,并不是手势识别器接收到事件的时机,而是手势识别器成功识别事件后的时机,是手势识别器的状态变为UIGestureRecognizerStateRecognized;
验证:自定义TestTapGestureRecognizer
-[TestTapGestureRecognizer touchesBegan:withEvent:]
-[AView touchesBegan:withEvent:]
-[TestTapGestureRecognizer touchesEnded:withEvent:]
-[AView tapGesture]
-[AView touchesCancelled:withEvent:]
问题2,
单独给 FartherAView 添加一个UIPanGestureRecognizer 手势, 然后在FartherAView上添加一个Aview,点击Aview,此时 AView 和FartherAView会调用哪些touch和UIGestureRecognizer哪些方法?
-[AView touchesBegan:withEvent:]
-[FartherAView touchesBegan:withEvent:]
/**调用tapGesture*/
-[FartherAView tapGestureFartherA]
-[AView touchesCancelled:withEvent:]
-[FartherAView touchesCancelled:withEvent:]
-[TestTapGestureRecognizer touchesBegan:withEvent:]
-[AView touchesBegan:withEvent:]
-[TestTapGestureRecognizer touchesEnded:withEvent:]
-[FartherAView tapGestureFartherA]
-[AView touchesCancelled:withEvent:]
问题3:
单独给 FartherAView 添加一个UIPanGestureRecognizer 手势, 然后在FartherAView上添加一个Aview,把Aview 的touch事件方法重写,不再继续传递touch事件, 点击Aview, 此时 UIPanGestureRecognizer 还能响应吗? AView 和FartherAView会调用哪些touch和UIGestureRecognizer哪些方法?
-[AView touchesBegan:withEvent:]
/**调用tapGesture*/
-[FartherAView tapGestureFartherA]
/**调用touchesCancelled*/
-[AView touchesCancelled:withEvent:]
验证版本:
-[TestTapGestureRecognizer touchesBegan:withEvent:]
-[AView touchesBegan:withEvent:]
-[TestTapGestureRecognizer touchesEnded:withEvent:]
-[FartherAView tapGestureFartherA]
-[AView touchesCancelled:withEvent:]
Window在将事件传递给hit-tested view之前,会先将事件传递给相关的手势识别器并由手势识别器优先识别。若手势识别器成功识别了事件,就会取消hit-tested view对事件的响应;若手势识别器没能识别事件,hit-tested view才完全接手事件的响应权;
事件最先传递给了手势识别器,然后传递给了最佳响应者,在手势识别器识别成功手势后,调用最佳响应者的touchesCancelled:方法终止最佳响应者对于事件的响应。
hook Window 的sendEvent方法;
屏幕快照 2019-12-05 下午3.20.23.png总的调用先后顺序
屏幕快照 2019-12-05 下午7.22.17.png总结:响应者链上 手势会被收集在一个数组里, 手势识别器比UIResponder具有更高的事件响应优先级!! 和UIResponder 的touch事件传递无关;
cancelsTouchesInView:
放开默认的取消touch事件,允许接受touch事件默认为YES。表示当手势识别器成功识别了手势之后,会通知Application取消响应链对事件的响应,并不再传递事件给hit-test view。若设置成NO,表示手势识别成功后不取消响应链对事件的响应,事件依旧会传递给hit-test view;
delaysTouchesBegan
delaysTouchesBegan = YES; 完全截断touch事件,不让touch调用一次
手势识别器成功识别了手势,独吞了事件,不会再传递给YellowView。因此只打印了手势识别器成功识别手势后的action调用。
允许多个手势同时识别共存
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
是否允许手势响应
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
网友评论