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

news2024/9/23 17:23:18

「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"];
    
    // 创建并设置UITableView
    self.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2105508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

都9月了,说好的系统规划与管理师改版教程呢?

今天距离2024年下半年的系统规划与管理师考试&#xff0c;还有66天的时间&#xff0c;六六大顺&#xff01; 差不多是一个月之前&#xff0c;系统规划与管理师的改版消息被刷屏了&#xff0c;刷屏的原因是系统规划与管理师改版的消息&#xff0c;据说新版大纲和教材已经完成备案…

每日OJ_牛客_五子棋(判断是否有赢)

目录 牛客_五子棋&#xff08;判断是否有赢&#xff09; 解析代码 牛客_五子棋&#xff08;判断是否有赢&#xff09; 五子棋__牛客网 题目&#xff1a; 用例输入&#xff1a; .................... .................... .................... .................... ....…

Nginx: 模块化管理编译安装与配置结构

Nginx 模块化管理机制 Nginx 是一个高性能web和反向代理服务器&#xff0c;尤其是在激烈的Web服务器竞争领域中能够依然保持很好的这个发展势头&#xff0c;并且在现在的众多企业中得到一个广泛的应用&#xff0c;这一切其实跟Nginx架构设计是分不开的 另外高度模块化的设计也…

计算机毕业设计 二手闲置交易系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

影石相机tf内存卡数据格式化后恢复方法

在数字化时代&#xff0c;‌相机成为了我们记录生活、‌捕捉瞬间的重要设备。‌影石相机&#xff0c;‌以其出色的性能和便捷的操作&#xff0c;‌赢得了众多摄影爱好者的青睐。‌然而&#xff0c;‌在使用过程中&#xff0c;‌我们可能会遇到一些意外情况&#xff0c;‌如不小…

PTA L1-041 寻找250

L1-041 寻找250&#xff08;10分&#xff09; 对方不想和你说话&#xff0c;并向你扔了一串数…… 而你必须从这一串数字中找到“250”这个高大上的感人数字。 输入格式&#xff1a; 输入在一行中给出不知道多少个绝对值不超过1000的整数&#xff0c;其中保证至少存在一个“2…

828华为云征文:华为云 Flexus X 实例性能测评——SuperBench 一键窥见性能

今天我拿到了华为云 Flexus X 实例&#xff0c;这款云服务是华为云推出的有一款明星产品&#xff0c;面向零售、金融、游戏等行业大多数通用工作负载场景。这次&#xff0c;我们就来测评一下它的性能到底怎么样&#xff01; Flexus 云服务 X 实例 在测评之前&#xff0c;我们…

星河产业应用创新奖,AI产业实干者的闪耀时刻

华晨宝马汽车有限公司搭建企业级 Agent 服务平台&#xff0c;推理成本降低50%&#xff08;获2023年星河产业应用创新奖&#xff09; 泰康保险集团股份有限公司实现保险理赔场景数字化升级&#xff0c;理赔作业效率提升27%&#xff08;获2022年飞桨产业应用创新奖&#xff09; …

【重学 MySQL】七、MySQL的登录

【重学 MySQL】七、MySQL的登录 MySQL 服务的启动与停止启动MySQL服务停止MySQL服务 MySQL 自带客户端的登录与退出登录登录步骤 退出注意事项 MySQL 服务的启动与停止 MySQL服务的启动与停止是数据库管理中的基本操作。 启动MySQL服务 通过“服务”窗口启动&#xff1a; 点击…

工作加速器:五种思维导图策略优化你的日常

思维导图是一种非常有效的图形工具&#xff0c;它能够清晰地展示发散性思维&#xff0c;并帮助我们将大量信息有序地组织起来。这种工具在日常生活和工作中都非常实用&#xff0c;因其简洁高效的特点&#xff0c;受到了广泛的欢迎。无论是在头脑风暴、项目规划&#xff0c;还是…

【AI-本地部署大模型】Centos虚拟机部署MaxKB、Ollama【基于大语言模型的知识库问答系统】

Ollama特点Centos部署docker部署脚本安装 通义千问包下载 MaxKB部署docker部署控制台 配置模型设置 Ollama Ollama 是一个开源项目&#xff0c;旨在让用户能够在本地计算机上轻松运行大型语言模型&#xff08;LLM&#xff09;。通过 Ollama&#xff0c;用户可以部署和运行多种…

显微镜基础知识--脑机起步

一、显微镜类别 学生级、实验级、研究级生物显微镜单目型、双目型、三目型生物显微镜 二、显微镜基础原理 &#xff08;1&#xff09;光学显微镜 光学显微镜主要由目镜、物镜、载物台和反光镜(集光镜)组成。目镜和物镜都是凸透镜&#xff0c;焦距不同。物镜的凸透镜焦距小于…

【算法每日一练及解题思路】计算以空格隔开的字符串的最后一个单词的长度

【算法每日一练及解题思路】计算以空格隔开的字符串的最后一个单词的长度 一、题目&#xff1a;计算以空格隔开的字符串的最后一个单词的长度 二、举例&#xff1a; 输入&#xff1a;hello nowcoder 输出&#xff1a;8 说明&#xff1a;最后一个单词为nowcoder&#xff0c;长…

PDF如何转化成Word文档?看完学会转换

PDF如何转化成Word文档&#xff1f;在数字化办公和学习的日常中&#xff0c;PDF文档因其出色的稳定性和跨平台兼容性而广受欢迎。然而&#xff0c;当我们需要编辑或修改PDF中的内容时&#xff0c;往往会遇到一些障碍&#xff0c;因为PDF格式本身并不支持直接编辑。这时&#xf…

c++类与对象的static成员

一.概念 用static修饰的成员变量称为类的静态成员变量&#xff0c;static修饰的成员函数称为类的静态成员函数。 如何声明和定义static成员 声明&#xff1a; 在定义类时进行声明 定义&#xff08;类外定义&#xff09;&#xff1a; static成员的定义/初始化必须在类外&am…

【C++ 面试 - STL】每日 3 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

3.js - Water2不显示水波纹

文中需要的资源&#xff0c;在我的资源那里能下载 【3.js 有2个水的生成方式&#xff1a;Water、Water2】 注意&#xff1a; 这个问题&#xff0c;是基于 Water2的 如下方式&#xff0c;不显示波纹 import * as THREE from three import { OrbitControls } from three/exam…

人工智能相关学科的关系

禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码

C++——类与对象(一)

目录 引言 面向过程与面向对象 1.面向过程 2.面向对象 类 1.类的定义 2.类的访问限定符 3.类域 4.类域与其他作用域 对象 1.类对象的实例化 2.对象的大小 2.1 如何计算对象的大小 2.2 内存对齐规则 2.3 示例 3.this指针 3.1 this指针的引入 3.2 this指针的详…

Nacos Config的配置中心

1.创建一个新的文件 2.导入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven…