「OC」剪不断,理还乱——UIResponder、UIGestureRecognizer、UIControl的响应优先级探究

本文主要是介绍「OC」剪不断,理还乱——UIResponder、UIGestureRecognizer、UIControl的响应优先级探究,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

「OC」剪不断,理还乱——UIResponder、UIGestureRecognizer、UIControl的响应优先级探究

文章目录

  • 「OC」剪不断,理还乱——UIResponder、UIGestureRecognizer、UIControl的响应优先级探究
    • 前言
    • 介绍
      • UIResponder
      • UIGestureRecognizer
      • UIControl
    • 正文
      • UIGestureRecognizer和UIResponder
        • 短按
        • 快速按下
        • 长按
        • 补充
    • 乱上加乱
      • 在view上添加点击手势识别器
      • 在button上添加手势识别器
    • 总结
    • 参考资料

前言

上一篇我们记录了编译器是如何找到最佳响应者以及响应者链的构成「OC」初识iOS事件处理流程,那么今天我们就来探究,UIResponder、UIGestureRecognizer、UIControl在处理事件时的优先级,以及相关的细节。让我们开始UIResponder、UIGestureRecognizer、UIControl的爱恨情仇吧

介绍

先简单介绍一下本文的三个主角

UIResponder

UIResponder是iOS中处理事件的核心类,它是许多重要UI类的父类,包括UIApplication、UIView和UIViewController。

主要特点:

  • 定义了响应和处理事件的接口
  • 是构建响应者链的基础
  • 可以处理触摸事件、运动事件、远程控制事件等

UIGestureRecognizer

UIGestureRecognizer是一个抽象基类,用于检测特定的触摸手势。

主要特点:

  • 简化了复杂手势的识别过程
  • 可以添加到任何UIView上
  • 有多个具体子类,如UIPanGestureRecognizer, UITapGestureRecognizer等

补充:其实手势接受事件也是通过与其他两者类似的方式,即触摸开始——触摸移动——触摸取消——触摸结束,他们被写在了UIGestureRecognizerSubclass.h之中,我们下文探究触摸优先级时,打印日志需要在UITapGestureRecognizer的子类之中引入 import <UIKit/UIGestureRecognizerSubclass.h>

手势分为离散型手势(discrete gestures)和持续型手势(continuous gesture)。系统提供的离散型手势包括点按手势(UITapGestureRecognizer)和轻扫手势(UISwipeGestureRecognizer),其余均为持续型手势。

在手势之中,有三个属性比较重要

@property(nonatomic) BOOL cancelsTouchesInView;
@property(nonatomic) BOOL delaysTouchesBegan;
@property(nonatomic) BOOL delaysTouchesEnded;

cancelsTouchesInView:默认值为YES,表示当手势识别器成功识别了手势之后,会通知Application取消响应链对事件的响应,并不再传递事件给hit-test view。若设置成NO,表示手势识别成功后不取消响应链对事件的响应,事件依旧会传递给hit-test view。

delaysTouchesBegan:默认为NO。默认情况下手势识别器在识别手势期间,当触摸状态发生改变时,Application都会将事件传递给手势识别器和hit-tested view;若设置成YES,则表示手势识别器在识别手势期间,截断事件,即不会将事件发送给hit-tested view。

delaysTouchesEnded:默认为YES。当手势识别失败时,若此时触摸已经结束,会延迟一小段时间(0.15s)再调用响应者的 touchesEnded:withEvent:;若设置成NO,则在手势识别失败时会立即通知Application发送状态为end的touch事件给hit-tested view以调用 touchesEnded:withEvent: 结束事件响应。

UIControl

UIControl是一个抽象基类,为特定的用户界面元素(如按钮、开关、滑块等)提供了基础结构。它的继承了UIView,而UIView又继承于UIResponder,所以在某些时候我们可以将两者等同看待,在细节方面有相似之处

主要特点:

  • 继承自UIView
  • 定义了一组标准的控制事件(如触摸按下、抬起、拖动等)
  • 允许为不同事件添加多个目标动作

target-action

  • target:处理交互事件的对象
  • action:处理交互事件的方式

UIControl作为能够响应事件的控件,必然也需要待事件交互符合条件时才去响应,因此也会跟踪事件发生的过程。不同于UIResponder以及UIGestureRecognizer通过 touches 系列方法跟踪,UIControl有其独特的跟踪方法:

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)cancelTrackingWithEvent:(nullable UIEvent *)event;

大致方法其实与我们先前接触的UIResponder的那4个方法相同,毕竟UIControl本身也是UIResponder的子类,但是这个UIControl之中的tracking系列的方法是在touch系列之中进行实现的。

具体的target-action具体流程,我按着网上给出的内容叙述一遍:

UIControl控件通过 addTarget:action:forControlEvents: 添加事件处理的target和action,当事件发生时,会调用 sendAction:to:forEvent: 将target、action以及event对象发送给全局应用,Application对象再通过 sendAction:to:from:forEvent: 向target发送action。

img

另外,若不指定target,即 addTarget:action:forControlEvents: 时target传空,那么当事件发生时,Application会在响应链上从上往下寻找能响应action的对象。

正文

接下来我们来探究一下,事件产生的先后优先顺序

我们先写一个简单的demo进行测试

#import "ViewController.h"@interface ViewController () <UITableViewDelegate, UITableViewDataSource>@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *dataSource;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.title = @"手势冲突测试";self.view.backgroundColor = [UIColor whiteColor];// 初始化数据源self.dataSource = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8"];// 创建并设置UITableViewself.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];self.tableView.delegate = self;self.tableView.dataSource = self;self.tableView.backgroundColor = [UIColor orangeColor];self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;[self.view addSubview:self.tableView];UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionTapView)];[self.view addGestureRecognizer:tap];UIButton *bottomButton = [UIButton buttonWithType:UIButtonTypeCustom];[bottomButton setTitle:@"点我无压力" forState:UIControlStateNormal];[bottomButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];bottomButton.backgroundColor = [UIColor cyanColor];bottomButton.frame = CGRectMake(20, CGRectGetMaxY(self.view.bounds) - 60, CGRectGetWidth(self.view.bounds) - 40, 40);[bottomButton addTarget:self action:@selector(buttonTap) forControlEvents:UIControlEventTouchUpInside];[self.view addSubview:bottomButton];
}-(void)buttonTap {NSLog(@"button clicked!");
}#pragma mark - UITableViewDataSource- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {return self.dataSource.count;
}- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {return 55;
}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {static NSString *cellIdentifier = @"Cell";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];if (!cell) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];cell.backgroundColor = [UIColor orangeColor];cell.textLabel.textColor = [UIColor whiteColor];}cell.textLabel.text = self.dataSource[indexPath.row];return cell;
}#pragma mark - UITableViewDelegate- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {NSLog(@"cell selected!");
}- (void)actionTapView{NSLog(@"backview taped");
}@end

我们进行点击进行尝试

Sep-03-2024 21-21-52

可以发现当我短按cell的时候,触发的是手势事件,只有长按cell才能够触发点击cell的方法,这到底是为什么啊?

由于这三兄弟,都实现了类似的方法,所以我在他们的子类之中,增加的打印日志的行为,以探究这三者的运行关系

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

具体打印日志的方式如下

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{NSLog(@"%s",__func__);[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{NSLog(@"%s",__func__);[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{NSLog(@"%s",__func__);[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{NSLog(@"%s",__func__);[super touchesCancelled:touches withEvent:event];
}

UIGestureRecognizer和UIResponder

关于UIGestureRecognizer和UIResponder的问题,其实上面UIGestureRecognizer的属性已经给我们透露一些相关的内容了,那我们就先从我们最有疑问的开始探究吧。

短按

image-20240904155530051

在短按的时候我们可以看到,其实在我们短按的时候,手势识别先开始,而tableView的触摸在随后也开始了,之所以我们的无法触发选择cell的方法是因为,cell的触摸在手势触发之后被Application给取消了。

引用官方文档的翻译:Window在将事件传递给hit-tested view之前,会先将事件传递给相关的手势识别器并由手势识别器优先识别。若手势识别器成功识别了事件,就会取消hit-tested view对事件的响应;若手势识别器没能识别事件,hit-tested view才完全接手事件的响应权。

因此通过这个点,我们可以总结出:手势识别器比UIResponder具有更高的事件响应优先级!!

快速按下

有的读者可能就有疑惑了,咋一听,如果短按的道理是上面解释的那样,那么按理来说快速按下的响应过程和短按应该是相同的才对。

我们知道UITableView是UIScrollView的子类,而UIScrollView因为要滚动,所以对事件做了特殊的处理: 当UIScrollView接收到事件之后,会暂时劫持当前的事件,会产生一个UIScrollViewDelayedTouchesBeganGestureRecognizer的手势识别器识别是否进行滚动操作,由于手势操作在应用之中可以进行同时识别,所以当UIScrollView这个机制劫持了触摸事件,和TableViewtouchesBegan: 的调用时间相差大概为0.15s,由于UIScrollViewDelayedTouchesBeganGestureRecognizer拦截了touchesBegan: 发送,当我们点击时间过短的话,没过延迟时间触摸就结束了,因此就会出现以下情况。

请添加图片描述

长按

由于我们手势劫持判断是有时效的,当时效结束之后,hit-tested view才完全接手事件,停止向手势识别器发送事件,仅向hit-test view发送事件。

image-20240904155620433

补充

刚刚我们讲的是离散型的手势,接下来我们补充一下持续性手势,仍是相同的内容但是把手势改为持续性的手势,

image-20240904200533662

我们可以看到即便滑动手势识别器识别了手势,Application也会依旧发送事件给tableView。

另外,在滑动的过程中,若手势识别器未能识别手势,则事件在触摸滑动过程中会一直传递给hit-tested view,直到触摸结束。

乱上加乱

如果让本就纠缠不清,复杂无比的UIGestureRecognizer和UIResponder,再有第三者UIControl插足呢?

真是头大,看的我都快看不懂字了。

根据官方文档,总结内容如下:UIControl会阻止父视图上的手势识别器行为,也就是UIControl处理事件的优先级比UIGestureRecognizer高,但前提是相比于父视图上的手势识别器。

我们设置一下情景

请添加图片描述

在view上添加点击手势识别器

image-20240904210011938

点击button后,事件先传递给手势识别器,再传递给作为hit-tested view存在的button(UIControl本身也是UIResponder),由于button阻止了父视图BlueView中的手势识别器的识别,导致手势识别器识别失败,button完全接手了事件的响应权,事件最终由button响应;

在button上添加手势识别器

请添加图片描述

点击button后,事件先传递给手势识别器,再传递给作为hit-tested view存在的button(UIControl本身也是UIResponder),button未阻止其本身绑定的手势识别器的识别,因此手势识别器先识别手势并识别成功,而后通知Application取消响应链对事件的响应,因为 touchesCancelled 被调用,同时 cancelTrackingWithEvent 跟着调用,因此button的target-action得不到执行。

所以我们可以得出结论:

UIControl比其父视图上的手势识别器具有更高的事件响应优先级。

总结

最后我们可以笼统的得出一个结论:UIRespnder、UIGestureRecognizer、UIControl,笼统地讲,事件响应优先级依次递增。

参考资料

01 触摸事件传递

iOS事件处理

iOS——事件、响应链和传递链

iOS触摸事件全家桶

UIGestureRecognizer

这篇关于「OC」剪不断,理还乱——UIResponder、UIGestureRecognizer、UIControl的响应优先级探究的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1139216

相关文章

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Codeforces Round #240 (Div. 2) E分治算法探究1

Codeforces Round #240 (Div. 2) E  http://codeforces.com/contest/415/problem/E 2^n个数,每次操作将其分成2^q份,对于每一份内部的数进行翻转(逆序),每次操作完后输出操作后新序列的逆序对数。 图一:  划分子问题。 图二: 分而治之,=>  合并 。 图三: 回溯:

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

web群集--nginx配置文件location匹配符的优先级顺序详解及验证

文章目录 前言优先级顺序优先级顺序(详解)1. 精确匹配(Exact Match)2. 正则表达式匹配(Regex Match)3. 前缀匹配(Prefix Match) 匹配规则的综合应用验证优先级 前言 location的作用 在 NGINX 中,location 指令用于定义如何处理特定的请求 URI。由于网站往往需要不同的处理方式来适应各种请求,NGINX 提供了多种匹

简单的角色响应鼠标而移动

actor类 //处理移动距离,核心是找到角色坐标在世界坐标的向量的投影(x,y,z),然后在世界坐标中合成,此CC是在地面行走,所以Y轴投影始终置为0; using UnityEngine; using System.Collections; public class actor : MonoBehaviour { public float speed=0.1f; CharacterCo

【Linux】探索进程优先级的奥秘,解锁进程的调度与切换

目录 进程优先级: 是什么? 为什么存在进程优先级的概念呢? Linux为什么调整优先级是要受限制的? PRI vs NICE Linux的调度与切换 概念准备: 那我们到底怎样完成进程的调度和切换呢? 区分:寄存器VS寄存器的内容 Linux实现进程调度的算法,需要考虑优先级,考虑进程饥饿问题,考虑效率问题。 解决优先级问题: 解决进程饥饿问题: 解决效率的问题:

【MATLAB】运算符及其优先级

在MATLAB语言中,运算符可以灵活组合以构建更复杂的运算表达式。需要注意的是,与其他高级编程语言类似,MATLAB中的运算符也有优先级。掌握运算优先级有助于我们正确地执行复杂的计算。以下是按照从高到低顺序排列的MATLAB运算符及其优先级列表。 括号 ()。数组的转秩 ( .’ ),数组幂 ( .^ ) ,复转秩 ( ’ ),矩阵幂 ( ^ )。代数正 ( + ),代数负 ( - ),逻辑非

复杂SQL集合(不断收集中)

1.一道SQL语句面试题,关于group by 表内容: 2005-05-09 胜 2005-05-09 胜 2005-05-09 负 2005-05-09 负 2005-05-10 胜 2005-05-10 负 2005-05-10 负 如果要生成下列结果, 该如何写sql语句?             胜 负 2005-05-09 2 2 2005-05-10 1 2 --------

【Java编程的逻辑】堆与优先级队列PriorityQueue

完全二叉树 & 满二叉树 & 堆 基本概念 满二叉树是指除了最后一层外,每个节点都有两个孩子,而最后一层都是叶子节点,都没有孩子。 满二叉树一定是完全二叉树,但完全二叉树不要求最后一层是满的,但如果不满,则要求所有节点必须集中在最左边,从左到右是连续的,中间不能有空的。 特点 在完全二叉树中,可以给每个节点一个编号,编号从1开始连续递增,从上到下,从左到右 完全二叉树有一

探究零工市场小程序如何改变传统兼职模式

近年来,零工市场小程序正逐渐改变传统的兼职模式,为求职者和雇主提供了一个更为高效、便捷的平台。本文将深入探讨零工市场小程序如何影响传统兼职模式,以及它带来的优势和挑战。 一、背景与挑战 传统的兼职市场往往存在信息不对称的问题,求职者难以快速找到合适的工作,而雇主也难以找到匹配的劳动力。此外,兼职工作的不稳定性和安全性也是求职者关注的焦点。零工市场小程序的兴起,旨在解决这些问题,通过数字化手