iOS 响应者链和事件传递

iOS事件

  • 触摸事件(touch events)
  • 按压事件(press events)
  • 摇晃事件(shake - motions events)
  • 远程控制事件 (remote - controls events)
  • 编辑菜单消息事件(editing menu messages)

这篇博客主要以触摸事件为例

传递过程

  • 主窗口接收到应用传过来的事件后,首先判断能不能接收事件。如果能,就会判断触摸点在不在自身范围内。(通过pointInside:withEvent判断)如果不在,就不处理。如果在就进行第二步。
  • 如果触摸点在自己的坐标范围内,那么窗口会从后往前遍历自己的子控件,来寻找最合适的view。(通过hitTest:withEvent:方法递归寻找)
  • 遍历到每一个子控件后,又会重复上面的两个步骤(传递事件给子控件,1.判断子控件能否接受事件,2.点在不在子控件上)
  • 按照上面步骤循环遍历子控件,直到找到最合适的view,如果没有更合适的子控件,那么自己就是最合适的view。

响应者链过程示意图

在这里插入图片描述

代码实现

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha <= 0.01 || self.userInteractionEnabled == NO || self.hidden) {
        return nil;
    }
    
    BOOL inside = [self pointInside:point withEvent:event];
    if (inside) {
        NSArray *subViews = self.subviews;
        // 对子视图从上向下找
        for (NSInteger i = subViews.count - 1; i >= 0; i--) {
            UIView *subView = subViews[i];
            CGPoint insidePoint = [self convertPoint:point toView:subView];
            UIView *hitView = [subView hitTest:insidePoint withEvent:event];
            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
    return nil;
}

方法讲解

//搜索是在哪个视图
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
//判断是否在这个视图范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

UIResponder 响应者对象

UIResponder是iOS中用于处理用户事件的API,可以处理触摸事件、按压事件(3D touch)、远程控制事件、硬件运动事件。可以通过touchesBegan、pressesBegan、motionBegan、remoteControlReceivedWithEvent等方法,获取到对应的回调消息。UIResponder不只用来接收事件,还可以处理和传递对应的事件,如果当前响应者不能处理,则转发给其他合适的响应者处理。

应用程序通过响应者来接收和处理事件,响应者可以是继承自UIResponder的任何子类,例如UIView、UIViewController、UIApplication等。当事件来到时,系统会将事件传递给合适的响应者,并且将其成为第一响应者。

第一响应者未处理的事件,将会在响应者链中进行传递,传递规则由UIResponder的nextResponder决定,可以通过重写该属性来决定传递规则。当一个事件到来时,第一响应者没有接收消息,则顺着响应者链向后传递。

UIResponder内部提供的响应和处理事件的方法

UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件

一根或者多根手指开始触摸view,系统会自动调用view的下面方法

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

一根或者多根手指离开view,系统会自动调用view的下面方法

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

  • touches 里包含了一个或多UITouch对象,也即一个或多个手指同时触摸view,因此touches.count就是触摸的点数,是1就是单点触摸,大于1就是多点触摸。
  • 一次触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数
    如果两根手指同时触摸一个view,那就是一个事件,view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
  • 如果这两根手指一前一后分开触摸同一个view,那就是两个事件,view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象

上面方法有两个参数,UITouch和UIEvent,解释如下:

UITouch

介绍

当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象,一根手指对应一个UITouch对象,它保存着跟手指相关的信息,比如触摸的位置,时间,阶段等,当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置,当手指离开屏幕时,系统会销毁相应的UITouch对象

主要属性和方法

//触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
 
//触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView *view;
 
//短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;
 
//记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;
 
//当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;

//获取手指与屏幕的接触半径 IOS8以后可用 只读
@property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0);

//获取手指与屏幕的接触半径的误差 IOS8以后可用 只读
@property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0);

//获取触摸手势
@property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);

//获取触摸压力值,一般的压力感应值为1.0 IOS9 只读
@property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);

//获取最大触摸压力值
@property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0);

//取得在指定视图的位置
// 返回值表示触摸在view上的位置
// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0,0))
// 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
- (CGPoint)locationInView:(nullable UIView *)view;

//该方法记录了前一个触摸点的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;

UIEvent

介绍

称为事件对象,记录事件产生的时刻和类型。每产生一个事件,都会产生一个UIEvent对象。

主要属性和方法

 //事件类型
 @property(nonatomic,readonly) UIEventType    type;

//事件子类型
 @property(nonatomic,readonly) UIEventSubtype  subtype;

//事件产生的时间
@property(nonatomic,readonly) NSTimeInterval  timestamp;

//返回值:返回与接收器相关联的所有触摸对象。
- (nullable NSSet <UITouch *> *)allTouches;
 
// 返回值:返回属于一个给定视图的触摸对象,用于表示由接收器所表示的事件。
- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
 
//返回值:返回属于一个给定窗口的接收器的事件响应的触摸对象。
- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;

//返回值:返回触摸对象被传递到特殊手势识别
- (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture 

UIView不能接收事件的三种情况

  1. 不允许交互 userInteractionEnabled = NO
  2. 透明度 alpha < 0.01
  3. 父视图或者子视图的 hidden = YES

参考文献

iOS UITouch事件处理-原理篇
iOS响应者链和事件传递

猜你喜欢

转载自blog.csdn.net/streamery/article/details/105321518