推广

iOS触摸事件详解

iseeyu3年前 (2024-02-21)推广155

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。

image.png

image.png

image.png

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、响应链之间的关系

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事件机制

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://www.0291.com.cn/post/56754.html

相关文章

西安企业做百度推广,一个月花三千和花三万到底差在哪?

4|最近跟几个西安的老板聊天,发现一个挺有意思的现象:同样做百度推广,有人一个月花三千块,询盘不断;有人一个月砸三万,水花都没见着。问他们区别在哪,大多数人说不清楚,只知道'钱花了,没效果'。 5| 6|百度推广这东西,说复杂也复杂,说简单也简单。核心就一件事:让你的潜在客户搜某个词的时候,你的信息...

我想当策划,大学应该选什么专业?

我想当策划,大学应该选什么专业?

学,广告,传播,公关,这些专业其实都可以。另外也可以学像汉语言文学,以及一些跟文学相关的一些专业,会对你找工作有帮助。但是,你想从事这个这份职业,除了专业的学习之外,更多的是,你要在上学的时候积极参与一些更营销策划相关的社会实践。比如说你可以做一些兼职,比如说帮别人去写稿,...

服装市场营销策略分析,“定位不慎满盘皆输”

服装市场营销策略分析,“定位不慎满盘皆输”

不同的区域需要不同的策略,而不同的营销策略需要不同的销售思路来配合。因此,在研究女装产品销售思路之前,最好先对于市场与营销策略做一次深入地了解。1.市场区域化(market segmentation):目标营销的第一步,是将整个市场拆分成若干个同性质的小市场。2.目标市场的...

晚间悦读丨你的思维方式,决定你的人生高度!

晚间悦读丨你的思维方式,决定你的人生高度!

越长大,越发现,能够决定你人生高度的,往往是你的认知层次。 你的认知在哪个层级,你的人生就处在什么状态。你认知的高度,决定了你人生的高度。 那如何才能升级方式、提升认知能力呢?可以从这三个方面做出改变。 NO. 1 改变态度 有人...

编写项目营销策划方案六大要点

编写项目营销策划方案六大要点

现在商业发展非常迅速,的需求非常的大,但是要做好营销策划是有一定难度的,我把自己做营销策划的一些体会在这里分享给朋友们。营销策划方案六大要点我们先说项目营销策划方案这个话题。大部分的营销策划人都是在企业里工作,大部分项目之初都是来源于一个想法,创始人、老板或者创始团队的一个...

【商场营销策划】如何进行商场营销策划?

【商场营销策划】如何进行商场营销策划?

商场是策划者们关注的永恒话题,那么究竟该如何作好商场营销策划呢?掌握以下这几点营销策略设计原则,至关重要!1.义利兼顾的原则。商场在获取利润的同时要充分考虑到消费者的合法利益和商场的长远利益。在作商场活动营销策略方案的时候应该将这一点作为首要的原则。对商场有利而对顾客不利的...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片