美文网首页锻炼吃饭的家伙
iOS 触摸、事件和响应

iOS 触摸、事件和响应

作者: LoveY34 | 来源:发表于2019-04-29 14:24 被阅读1次
touch

掏出手机->解锁手机->点开APP->低头刷手机,这一套流程是现在每个手机党每天重复最多的一套连击操作,那么作为开发人员的你有没有考虑过,你点击触摸手机屏幕的时候,手机系统做了些什么可以及时响应用户呢?

一.触摸&&事件&&响应者
1.触摸

触摸是这一切的源头,这是用户跟手机最直接的交互方式,这个物理行为对应到代码层面就是一个UITouch对象

  • 一根手指触摸屏幕一次就生成一个UITouch对象。相应的,如果是多根手指触摸屏幕的话就会生成多个UITouch对象。
  • 如果是多根手指先后触摸的话,系统会根据触摸位置来判断是否应该更新同一个UITouch对象。
  • 如果两根手指先后触摸同一位置,或者一个时间间隔内同一根手指同时触摸同一位置的时候,系统会在第一次触摸的时候生成一个UITouch对象,第二次触摸的时候会更新UITouch对象的TapCount值(1->2),当然了位置不一样的话就会生成两个没有关系的UITouch对象。
  • 手指离开屏幕并在一定时间内没有再次点击同一个位置的话,该UITouch对象就会被释放,使用代码测试的这个一定的时间大概是1.56毫秒。
  • UITouch对象中包含很多信息,包括但是不限于时间戳、状态、点击次数、类别、力度等等,更多信息可以参考文档。
@property(nonatomic,readonly) NSTimeInterval      timestamp;
@property(nonatomic,readonly) UITouchPhase        phase;
@property(nonatomic,readonly) NSUInteger          tapCount;   // touch down within a certain point within a certain amount of time
@property(nonatomic,readonly) UITouchType         type NS_AVAILABLE_IOS(9_0);

// majorRadius and majorRadiusTolerance are in points
// The majorRadius will be accurate +/- the majorRadiusTolerance
@property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0);
@property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0);

@property(nullable,nonatomic,readonly,strong) UIWindow                        *window;
@property(nullable,nonatomic,readonly,strong) UIView                          *view;
@property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);

注意:使用demo去查看UITouch对象生成的过程中,需要把UIView的属性multipleTouchEnabled置为true,否则UIView每次只能响应一个触摸,不管几根手指。。(别问我是咋知道的😂)

2.事件

触摸等操作的目的是为了生成事件以供响应者处理,一个事件对应一个UIEvent对象。

  • iOS系统中的事件分很多种,每种事件对应的交互也不一样,通过UIEvent的type属性就可以区分出来,
typedef NS_ENUM(NSInteger, UIEventType) {
    UIEventTypeTouches,
    UIEventTypeMotion,
    UIEventTypeRemoteControl,
    UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
};

最常见的就是由触摸产生的触摸事件,还有按压(3D touch)事件、远程控制事件和硬件运动事件。

  • 以触摸事件为例,一个触摸事件可能是有多个触摸或者说是有多根手指触摸产生的,而触摸的对象都是可以通过事件对象的allTouches获取到。
3.响应者

事件产生后只有两种结果,要么就是被第一响应者接收并处理掉或者传递给别的响应者并处理掉,要么就是被第一响应者接收、传递但是这一过程中没有处理掉而被直接丢弃释放掉,这两种结果都需要一个重要的角色那就是响应者(UIResponder)。
响应者可以使任何继承自UIResponder的子类,像常见的UIViewUIViewControllerAppDelegateUIApplication等等。开发者可以通过UIResponder中的API去监听事件的生命周期,例如触摸事件的相关的几个API。

// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application.  Failure to
// do so is very likely to lead to incorrect behavior or crashes.
- (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;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
二.接收事件&&传递事件&&处理事件

触摸屏幕到最后响应触摸事件整个过程看似在瞬间就完成了,其实这中间还是分很多步骤的,大体可以分为两个过程。(本文涉及到的触摸事件处理过程更多的属于软件层次,不涉及硬件层次)

1.寻找事件的第一响应者

当前应用程序接收到触摸事件后,会把触摸(UITouch)包裹在事件对象(UIEvent)中,再把该事件对象插入到一个由应用程序的UIApplication维护的事件队列中,并由UIApplication按照队列顺序依次分发。一次分发一个事件,但是响应者(UIResponder)众多,那事件到底应该分发给哪一个响应者呢?所以这第一步就是寻找第一响应者的这么一个过程。(我一直没弄明白这个事件分发队列是由系统维护的还是由当前应用程序的UIApplication维护的,若是由当前应用程序的UIApplication维护的话,那如果此时手机在桌面上的时候呢?这时候没有启动任何APP的,难道桌面其实也是一个APP??有大佬可以解惑下吗?欢迎在评论区留言。。)

查找第一响应者的过程本质上就是不断调用UIView的API去做试探

注意这里还不涉及到UIResponder的API哦!涉及的关键API有如下,而且都是UIView的API,所以寻找第一响应者的过程也叫做hitTest过程。

 // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;  

简述一下查找第一响应者的过程就是:

  • 触摸事件被UIApplication接收后,会被插入到事件队列中等待分发。
  • UIApplication会将队列中的事件按照FIFO的顺序将事件出队列,首先分发给UIWindow对象,后显示的UIWindow对象优先级更高。
  • 若UIWindow对象不能响应事件,则将事件传递给同级的其他UIWindow对象,若可以响应事件,则按照子视图的添加顺序,优先询问后添加的子视图。
  • 若子视图不能响应事件,则将事件传递给同级的上一个子视图,若能响应,就遍历子视图,按照子视图的添加顺序,优先询问后添加的子视图。
  • 循环上面的操作,若当前视图能响应事件,但是子视图无法响应事件,那么当前视图就是最合适的响应者。
    上面的过程中涉及到两个问题:

a.如何判断当前视图能不能响应事件呢?

视图在以下情况下是不能响应事件的:

  • 不允许交互:userInteractionEnabled = NO
  • 隐藏:hidden = YES
  • 透明度小于等于0.01:alpha <= 0.01
  • 触摸点是否在视图的坐标范围内,通过下面的API判断:
// default returns YES if point is in bounds
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   

b.视图如何把事件传递给子视图呢?

把事件传递的方式子视图的方式就是让循环子视图并让子视图调用hitTest:withEvent:,返回的UIView对象,就是当前视图层次中的响应者。

  • 若当前视图无法响应事件,则返回nil
  • 若当前视图可以响应事件,按照子视图添加顺序从后往前遍历子视图,获取子视图中的事件响应者,并返回
  • 若当前视图可以响应事件,但是无子视图可以响应事件,那么就返回当前视图作为响应者
    推测hitTest:withEvent:的实现代码:
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event{
    if (!self.userInteractionEnabled ||
        !self.hidden ||
        self.alpha <= 0.01 ||
        ![self pointInside:point withEvent:event]) {
        //当前视图无法响应事件
        return nil;
    }
    
    __block UIView *fitView = self;
    __weak typeof(self) weakSelf = self;
    [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subView, NSUInteger idx, BOOL * _Nonnull stop) {
        CGPoint convertPoint = [weakSelf convertPoint:point toView:subView];
        UIView *subFitView = [subView hitTest:convertPoint withEvent:event];
        if (subFitView) {
            fitView = subFitView;
            *stop = YES;
        }
    }];
    
    return fitView;
}
2.事件的响应及传递

第一响应者找到后,它对事件具有处理权,它可以选择自己处理事件,也可以将事件传递给下一个响应者,而这个由响应者构成的视图链就称之为响应链。(敲黑板!这是重点,考试要考的。。)
响应者对于事件的拦截和传递都是通过touchesBegan:withEvent:方法控制的,任何一个UIResponder的对象都可以是响应者,对象中都会默认实现该方法,但是默认不会对事件做任何处理,只是将事件沿响应链传递,所以想要做自定义操作就需要重写方法。响应者一般对事件的处理方式有:

  • 默认操作,事件沿着默认的响应链向下传递
  • 处理事件并终止事件的传递
  • 处理事件,让事件继续往下传递

后两者的区别就在于有没有调用父类的touchesBegan:withEvent:方法。

响应链中的事件传递规则又是什么样的呢?

UIResponder有一个nextResponder API,用于获取当前响应者在响应链中的下一个响应者,因此第一响应者确定后,默认的响应链都已经通过nextResponder串起来了。
默认的nextResponder的规则

  • 若一个视图(UIView对象)是一个视图控制器(UIViewController对象)的根视图,那么该视图的nextResponder就是该视图控制器。否则该视图的nextResponder就是其父视图。
  • 若一个视图控制器(UIViewController对象)是window(UIWindow对象)的rootViewController,那么该视图控制器的nextResponder就是该window。否则其nextResponder为presenting view controller。
  • window(UIWindow对象)的nextResponder为UIApplication对象。
  • 若当前应用的app delegate是一个UIResponder对象,且不是UIView、UIViewController或app本身,则UIApplication的nextResponder为app delegate。

相关文章

  • iOS 触摸事件与响应理解

    参考文章: iOS触摸事件的流动 iOS触摸事件的传递与响应 UIViewController UIAppli...

  • 初识iOS事情处理机制

    参考:史上最详细的iOS之事件的传递和响应机制-原理篇iOS触摸事件全家桶史上最详细的iOS之事件的传递和响应机制...

  • 二、事件传递链和响应者链

    iOS触摸事件详解iOS开发-事件传递响应链 响应者链 UIResponser包括了各种Touch message...

  • iOS 触摸、事件和响应

    掏出手机->解锁手机->点开APP->低头刷手机,这一套流程是现在每个手机党每天重复最多的一套连击操作,那么作为开...

  • ios 事件传递和响应

    史上最详细的iOS之事件的传递和响应机制-原理篇iOS触摸事件传递响应之被忽视的手势识别器工作原理手势事件中can...

  • iOS 触摸事件的处理层次及原理

    iOS 事件包括:运动事件、远程控制事件、触摸事件。 其中触摸事件的响应流程是:当手指触摸屏幕时,会产生一个事件,...

  • iOS-触摸事件传递、事件响应者链

    前言,本文简单了解触摸事件传递和事件响应者链。 一、知识点简介 1.1 iOS中的事件介绍 iOS中的事件可以分为...

  • iOS开发之触摸事件

    本文介绍了iOS中使用频率较高的触摸事件,并阐述了事件产生和传递的过程,以及响应者链的事件传递过程 触摸事件 简介...

  • iOS-详解事件传递和响应者链

    事件响应 一、事件的分发和传递(确定事件的第一响应者): 1.当iOS程序中发生触摸事件后,系统会将事件加入到UI...

  • iOS事件的响应者链

    iOS 事件响应者链 1 iOS中的事件 触摸事件 加速计事件 远程控制事件 在iOS中不是任何对象都能处理事件,...

网友评论

    本文标题:iOS 触摸、事件和响应

    本文链接:https://www.haomeiwen.com/subject/perynqtx.html