优秀工程师-UIResponder

news/2023/6/8 19:58:13

在iOS中UIResponder类是专门用来响应用户的操作处理各种事件的,包括触摸事件(Touch Events)、运动事件(Motion Events)、远程控制事件(Remote Control Events,如插入耳机调节音量触发的事件)。我们知道UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder,UIWindow是直接继承自UIView的一个特殊的View,所以这些类都可以响应事件。当然我们自定义的继承自UIView的View以及自定义的继承自UIViewController的控制器都可以响应事件。iOS里面通常将这些能响应事件的对象称之为响应者。

下面我们根据UIResponder.h头文件来具体介绍关于响应事件的各个方面。

一. UIResponder类

这个部分我们主要介绍3种事件类型,即触摸事件,运动事件,远程控制事件。当用户触发某一事件时,UIKit会创建一个UIEvent事件对象(关于iOS事件对象可以参考这篇文章),事件对象会加入到一个FIFO先进先出的队列中,UIApplication对象处理事件时,会从队列头部取出一个事件对象进行分发。

1.触摸事件
@interface UIResponder : NSObject
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;//触摸屏幕
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;//在屏幕上移动
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;//离开屏幕
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;//系统事件干扰

这4个方法是触摸事件的最原始处理的4个方法,分别代表触摸屏幕,在屏幕上移动,离开屏幕以及受到系统优先级别高的事件的干扰(比如来电话)取消触摸事件。另外UIKit框架对于触摸事件为我们提供了UIGestureRecognizer手势识别这个类,基本上能满足我们的大部分需求(可以参考这篇文章)。这里介绍的是最底层的处理方法,比如可以用来实现绘图类型的APP.
上面提到UIApplication对象从队列中取出事件对象进行分发,对于触摸事件来说,UIApplication会首先把事件交给keyWindow,Window会将事件交给UIGestureRecognizer处理,如果UIGestureRecognizer识别了传递过来的事件,则交给相对应的target去处理(关于iOS手势事件可以参考这篇文章),事件不会再传递,如果UIGestureRecognizer并没有识别传递过来的事件(可能是没有视图添加手势,也可能手势识别不成功),事件会传递到视图树形结构,会分成寻找接受者和事件响应这两个步骤。
1.在iOS视图树形结构中找到最终的接收者,也就是触摸事件发生的那个最上层的View上,这一过程称为hit-testing(测试命中),通过一层层的遍历找到最终的命中视图称为hit-test view.
UIView中有两个方法用来确定hit-test view.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; 
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

hit-testing


这是一张我从apple官方文档里面的截图,这里所有的显示的View都是加载到主window上,假设我们触摸到屏幕上ViewD的区域,当我们没有重载UIView的hitTest:withEvent:pointInside:withEvent:这两个方法时,系统默认的处理如下:

  • keyWindow调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes,遍历keyWindow的subView->ViewA.
  • ViewA调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes,遍历ViewA的subView->ViewB、ViewC(关于ViewB和ViewC先执行哪个,是根据ViewA添加子控件的先后顺序,总是先执行后添加的subView.假设添加ViewB后添加ViewC)
  • ViewC调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes,遍历ViewC的subView->ViewD、ViewE
  • ViewE调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回NO,ViewE的hitTest:withEvent:返回nil(如果是先执行ViewB的情况,假设ViewB还有子节点subView,由于ViewB的pointInside:withEvent:返回NO,ViewB的hitTest:withEvent:`直接返回nil是不会再去遍历ViewB的子节点的)
  • ViewD调用pointInside:withEvent:判断触摸点是否在其frame范围内,返回Yes并且没有子节点subView,ViewD的hitTest:withEvent:返回ViewD本身,即为最终的hit-test view(不会再遍历ViewB)iewB

    需要注意的是:View.isHidden=YES View.alpha<=0.01 View.userInterfaceEnable=NO View.enable = NO(指继承自UIControl的View)的这4种情况下,View的pointInside返回NO,hitTest方法返回nil
    默认UIImageViewuserInterfaceEnable=NO

2.找到了hit-test view,下一个步骤就是响应事件。说明一下,对于触摸事件来说,无论View是否处理事件,即使是application通过[application beginIgnoringInteractionEvents]忽略了触摸事件,上面hit-testing的过程依然存在,它只影响第二个步骤事件响应的过程。下面我们将介绍iOS响应者链条(Responder chain)


responder chain

这是我从官方文档里面截取的一张关于响应者链条的截图。我们先看上图左边的情况:标注为①的地方即为步骤1找到的hit-test view 它作为第一响应者来响应这个事件,如果该view没有通过重写或者封装touch系列方法来处理该事件,默认touch的实现就是调用父类的touch方法,将事件传递下去。在这里由1->传递到它的父类2,2是控制器的根view,->传递到vc控制器->传递到窗口window->传递到application
再看上图右边的情况:标注为①的地方即为步骤1找到的hit-test view,同时它是控制器的根view并且还有父视图,事件传递到控制器->再传递到父视->传递到控制器,再传递到父视图窗口->application。其实上图左边部分也可以理解为窗口是控制器根视图的父视图。如果整个响应者链条结束,都没有对事件做处理,那么该事件会被丢弃。

总结一下响应者链条的传递过程是:由第一响应者(对于触摸事件来说是hist-test view)开始向上传递。如果该视图是控制器的根视图,先传递给控制器,再传递给父视图,如果不是控制器的根视图,直接传递给父视图。
只要在响应者的处理方法里面调用父类的方法,就可以让多个视图和控制器响应同一个事件,响应者链条的根本目的是:共享事件,让多个视图和控制器可以对同一事件做不同的处理。

2.运动事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);

这3个方法是运动事件的最原始处理的3个方法,这里处理的运动事件特指shake事件,手机摇动触发手机内部的加速度传感器,可以用来实现摇一摇计算运动的步数等等应用。类似于触摸事件,这3个方法分别代表事件开始、事件结束和受到系统干扰取消事件。
加速度计accelerometer实际上是由三个加速度计组成,分别用于测量X,Y和Z轴直线路径速度的变化。结合所有三个加速度计可以检测设备朝任何方向的运动和获取设备的当前方向。对于shake事件来说,我们不关心3个方向上的运动,只作为一个事件对象来处理。如果只是处理设备的大方向,并不需要知道方向向量,如横屏竖屏屏幕旋转,我们可以使用UIDevice类(参考文章)。如果我们需要知道3个方向上的运动做更细致化的处理,如上了多少层楼等运动类型APP,可以使用的核心运动框架访问加速度计,陀螺仪和设备的运动类来做处理(Core Motion参考文章)
上面介绍的响应者链条对shake事件同样适用,只不过,没有hit-testing过程,如果当前显示的视图界面没有一个view声明为第一响应者(调用becomeFirstResponder申明并且View需要重写canBecomeFirstResponder方法返回YES,默认返回为NO),默认当前视图控制器为第一响应者,并将事件沿着响应者链条传递,直到被处理。如果有视图声明为第一响应者,就从该视图开始传递事件直到被处理,如果该事件最终没有被处理并且UIApplication的applicationSupportsShakeToEdit属性为YES(默认就是YES),当键盘显示的时候,系统会有一个是否撤销正在输入的警告。就是微信和QQ上在输入的时候摇动手机提示撤销输入的那种效果。关于更多撤销方面的操作参考NSUndoManager



作者:pican
链接:http://www.jianshu.com/p/2dda99a0e09a
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

https://dhexx.cn/news/show-43310.html

相关文章

李洪强iOS开发拓展篇—UIDynamic(重力行为+碰撞检测)

iOS开发拓展篇—UIDynamic(重力行为碰撞检测) 一、重力行为 说明&#xff1a;给定重力方向、加速度&#xff0c;让物体朝着重力方向掉落 1.方法 &#xff08;1&#xff09;UIGravityBehavior的初始化 - (instancetype)initWithItems:(NSArray *)items; item参数 &#xff1a;里…

SwiftUI 教程之如何在 2 秒内实现自动滚动功能

使用 SwiftUI 进行开发令人惊讶。它确实使得只需几行代码就可以实现一些很酷的功能。今天,我们来实现自动滚动。当您创建聊天应用程序时,此技术特别有用。我将尽可能简要地解释它。 准备你的 UI struct AutoScrollList: View {var body: some View {VStack {Button("S…

【零基础学习iOS开发】【02-C语言】09-流程控制

前言 1.默认的运行流程 默认情况下&#xff0c;程序的运行流程是这样的&#xff1a;运行程序后&#xff0c;系统会按书写顺序执行程序中的每一行代码。比如下面的程序 1 #include <stdio.h>2 3 int main()4 {5 6 printf("Hello-1\n");7 printf(&qu…

SwiftUI 4 新功能之掌握 WeatherKit 和 Swift Charts

SwiftUI 4 新功能之掌握 WeatherKit 和 Swift Charts 今年在 WWDC22 上有很多令人兴奋的新框架和 API 让我们投入其中。然而,我对 WeatherKit 和 Swift Charts 感到非常兴奋,因为我们现在终于有了对天气数据和绘制数据集的原生 1 方支持。 在本文中,我们将研究如何将两者结…

iOS开发拓展篇—UIDynamic(捕捉行为)

iOS开发拓展篇—UIDynamic(捕捉行为) 一、简介 可以让物体迅速冲到某个位置&#xff08;捕捉位置&#xff09;&#xff0c;捕捉到位置之后会带有一定的震动 UISnapBehavior的初始化 - (instancetype)initWithItem:(id <UIDynamicItem>)item snapToPoint:(CGPoint)point; …

SwiftUI 完整项目之音乐学习卡片类App 开机引导页 图文列表页 详细设置页(教程含源码)

SwiftUI 完整项目之音乐学习卡片类App(教程含源码) 实战需求 这是一个基本的SwiftUI应用程序,可帮助音乐家了解可用的不同类型的麦克风以及如何最好地应用它们。 开机引导页图文列表页详细设置页本文价值与收获 看完本文后,您将能够作出下面的界面 看完本文您将掌握的技…

8.14. JSON Types

8.14. JSON Types8.14.JSON类型JSON data types are for storing JSON (JavaScript Object Notation) data, as specified in RFC 71591. Such data can also be stored as text, but the JSON data types have the advantage of enforcing that each stored value is valid ac…

Tkinter 教程之10个经典程序代码 数字时钟(教程含源码)

Tkinter 是 Python 编程语言中最好的模块之一。它专门用于创建图形用户界面。有了这个模块,我们可以创建一些很棒的程序。在本文中,我们尝试创建一个简单而有趣的程序。我们将特别关注简单的 GUI 以更好地理解 Tkinter。 目录 带有 Tkinter 的clock使用 Tkinter 更改颜色背景…

李洪强漫谈iOS开发[C语言-012]-C语言基本数据类型

// // main.m // 08 - 基本数据类型 // // Created by vic fan on 16/7/16. // Copyright © 2016年 李洪强. All rights reserved. // 基本数据类型 计算机中,C语言中有丰富的数据类型<Mac为标准> 有整数类型 int(4 个字节) short int(2个字节) long int(8个字…

GPU教程之使用 NVIDIA 显卡 (GPU) 设置深度学习工作场所 — 适用于 Windows 操作系统

在使用任何这些库之前,我们需要通过在我们的系统上安装和配置它们来设置我们的 PC 或笔记本电脑。虽然这些库可以安装在不同类型的操作系统上,但今天的讨论仅限于 Windows 操作系统。我还假设您有一台安装了最新版本 Windows 的笔记本电脑或 PC。 如何从 Anaconda 发行版安装…