iOS触摸事件详解

iOS触摸事件详解

image.png

结合图我们来看手势的整个迁移过程,先明确几个信息

1)手势的状态迁移,前提是收到Touch message,才能做状态变化处理代码。
2)手势分为连续状态手势、不连续状态手势。连续手势有长按,慢滑等。不连续手势有单击,双击等等。
3)当用户没有点击屏幕,所有手势都处于Possiable初始状态。
当用户点击屏幕,手势会收到Touch Began Message, 手势的touchBegan方法会被调用,手势开始记录点击位置和时间,仍处于 Possiable状态。

如果用户按住不放,间隔超过一定时间,单击手势会变化为Failed状态,并在下个一runloop变为possiable。

如果时间大于长按手势设定时间,长按手势就会变化为Began状态,当用户移动手指,长按手势的touch move方法被调用,长按手势将自己状态设置为Changed状态,并且也会回调处理方法。最后手指离开,系统调用长按手势touchEnd方法,手势状态设置为 Recognized状态。

4.3 混合手势处理

1)当给UIView添加多个UIGestureRecognizer对象时,默认只有1个生效。如果想全部都生效,让协议中的gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法返回YES。

2)同时添加单击和双击,也均允许生效。题来了,那双击屏幕时,默认触发1次单击事件和1次双击事件。但这不是想要的效果,如何实现双击时,只触发双击手势呢,单击时只触发单击手势呢?解决方案是让协议中的gestureRecognizer:shouldRequireFailureOfGestureRecognizer:方法、

gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:方法都返回YES。

4.4 UIGestureRecognizerDelegate协议
@protocol UIGestureRecognizerDelegate <NSObject>
@optional

// 手势状态是否允许更改,默认为YES。
// 如果实现中返回NO,那么手势最后都为失败状态。
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;

// 允许多个手势生效,默认为NO。
// 如果实现中返回YES,同时添加单击和双击手势,双击屏幕时,同时产生1次单击事件和1次双击事件
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

// 以下2个方法,为手势之间添加依赖,默认NO。
// 比如单击和双击,如果双击手势识别失败,转换为识别单击手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);

// 手势是否关注UITouch、UIPress对象状态变化,和gestureRecognizerShouldBegin:效果类似,默认为YES。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press;

@end
4.5 苹果封装以下几个手势

(1)UITapGestureRecognizer

单个或多个塞子。指定数量的手指必须要承认的姿态,挖掘查看指定的次数。

(2)UIPinchGestureRecognizer

看起来捏的手势,涉及两个接触。当用户将两个手指,向对方的传统意义是缩小;当用户将两个手指从彼此远离,传统意义变焦。

(3)UIRotationGestureRecognizer

看起来轮换涉及两个触摸手势。当用户移动手指对面对方的圆周运动,基本的观点应该在相应的方向和速度旋转。

(4)UISwipeGestureRecognizer

看起来刷卡在一个或多个方向的手势。抨击是一个独立的姿态,因此,相关的操作的消息发送每个手势只有一次。

(5)UIPanGestureRecognizer

看起来平移(拖动)的手势。用户必须按查看上一个或更多的手指,而他们平移。实施这个手势识别动作方法的客户端可以要求它目前的翻译和手势的速度。

(6)UILongPressGestureRecognizer

看起来长按手势。用户必须按下一个或更多的手指行动讯息传送至少指定期限。此外,手指可能要承认的姿态移动唯一指定的距离;如果他们超越这个限制的姿态失败。

5. 响应链

响应链由UIResponder对象为node,形成的一个链表状结构,通过UIResonder的nextResonder链接。
下图中节点关系是箭头方向朝上,也就是subView指向superView。

iOS触摸事件详解

image.png

iOS触摸事件详解

image.png

iOS触摸事件详解

image.png

iOS触摸事件详解

image.png

6.iOS触摸事件详解

事件传递是在响应链中查找hitTestView的过程,从父View到子View。事件转发是响应消息中触发的,从子View通过nextResponder在响应链中回溯。

6.1 事件传递

1)找到设备中的Application。

触摸屏幕时,由iOS系统的硬件进程获取,简单封装事件后暂存在系统中,利用端口实现与Application进程完成通信,将事件传递给Application进程。

当应用程序启动时,主线程的RunLoop会注册一个基于端口的source,当接收到相关事件时,主线程会被唤醒执行触摸事件。

2)通过响应链找到最终处理事件的hitTestView。

当Application接收到新的事件时,开始寻找响应链中的hitTestView。

将所有的显示在屏幕上的 “UIWindow对象”,按照层级结构从上到下排列成一个数组。从第一个UIWindow对象开始,先判断UIWindow是否不隐藏且可见度大于0.01且可交互,再判断点击位置在不在这个UIWindow内。

如果不在 ,返回nil, 就换下一个UIWindow;

如果在的话,并且UIWindow没有subView就返回自己,但如果UIWindow有subViews,就递归遍历整个subViews,直到找到hitTestView。

如果没有找到到就不做传递。

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event {
  //判断该视图是否满足响应事件的条件
    if (!self.hidden && self.alpha > 0.01 && self.isUserInteractionEnabled) {
        //判断点击位置是否在自己区域内部
        if ([self pointInside: point withEvent:event]) {
            UIView *attachedView;
            // 遍历子视图
            for (int i = self.subviews.count - 1; i >= 0; i--) {
                UIView *view  = self.subviews[i];
                // 对子view递归调用本方法
                attachedView =  [view hitTest:point withEvent:event];
                if (attachedView)
                    break;
            }
            if (attachedView)  {
                return attachedView;
            } else {
                return self;
            }
        }
    }
    return nil;
}
6.2 事件转发
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    //do someThiing
  [self.nextResponser touchesBegan: touches withEvent:event];
}
6.3 事件响应

当锁定hitTestView后,当触摸状态发生变化,会不停的收到UITouch Message消息,调用hitTestView从UIResponder类继承的方法。

// 点击刚开始,回调这个方法
- (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;

7.Touch Event、UIGestureRecognizer、响应链之间的关系

iOS触摸事件详解

image.png

7.1 关联详解

第一步:系统会将所有的Touch message优先发送给关联在响应链上的全部手势。手势根据Touch序列消息和手势基本规则更改自己的状态(有的可能失败,有的可能识别等等)。如果某个手势对Touch message成功拦截(被拦截时,系统不会将Touch message 发送给响应链顶部响应者),顶部视图控件调用touchesCancelled:withEvent方法,否则系统会进入第二步。

第二步:系统将Touch message发送给响应链顶部的视图控件,顶部视图控件这个时候就会调用Touch相关的四个方法中的某一个。

7.2 拦截

举例说明:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleClick)];
    gesture.numberOfTapsRequired = 2;
    self.view.userInteractionEnabled = YES;

    [self.view addGestureRecognizer:gesture];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesBegan");
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesEnded");
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesMoved");
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesCancelled");
}

- (void)doubleClick {
    NSLog(@"双击拉");
}

结果分析:
1)如果单击屏幕(拦截失败),打印
TestForMoreGesture[26097:18114462] touchesBegan
TestForMoreGesture[26097:18114462] touchesEnded
2)如果双击屏幕(拦截成功),打印
TestForMoreGesture[26097:18114462] touchesBegan
TestForMoreGesture[26097:18114462] 双击拉
TestForMoreGesture[26097:18114462] touchesCancelled

手势是否拦截该Touch Message,主要由UIGestureRecognizer类的三个属性控制。

// 默认为YES,表明当手势成功识别事件后,系统会将Touch cancel消息发送给hitTestView ,并调用hitTestView的TouchCancel。设置为NO,不会再收到TouchCancel
@property(nonatomic) BOOL cancelsTouchesInView;
// 默认为YES, 表明无论什么情况下,不会拦截Touch began消息。如果设置为NO,只要有一个手势不识别失败,都不会发送Touch began到响应链的第一响应者。
@property(nonatomic) BOOL delaysTouchesBegan; 
// 默认为NO, 和delaysTouchesBegan类似,不过它是用来控制TouchEnd message的拦截
@property(nonatomic) BOOL delaysTouchesEnded; 

总结

iOS整个事件处理的过程就是这样,系统为完成整个交互做了很多东西,核心点如下:

1、事件分发过程分为:1.事件消息传递;2.事件消息分发。

2、响应网是事件响应的基础,响应链是事件响应的具体路径。

3、事件消息分发优先发送给手势集合,手势内部会做冲突处理,过滤消息。不被过滤的消息会传递给响应链对象。

参考文章:
iOS触摸事件详解
深入浅出iOS事件机制

文章均来自互联网如有不妥请联系作者删除QQ:314111741 地址:http://www.mqs.net/post/13784.html

相关阅读

添加新评论