ios 触摸事件总结

2020-01-16  本文已影响0人  沧海小鱼儿

iOS里的触摸 UIResponder . UIGestureRecognizer

一:UIResponder :

触摸事件由触屏生成后如何传递到当前应用?

1510019-62b6b1eec26730aa.png

1,触摸响应核心方法,寻找 hit-tested view:

底层具体实现如下 :
- (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对象:

1510019-2a9a5bb5ed21bd43.png

响应四个方法:

//手指触碰屏幕,触摸开始
- (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

案例应用:

首页婴儿儿童浮层
上一篇下一篇

猜你喜欢

热点阅读