iOS中的触摸事件

一. iOS中的事件

iOS中的事件可以分为三大类:触摸事件加速计事件远程控制事件

二. 响应者对象

  1. 在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件,我们称之为“响应者对象”。
  2. UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。

三. UIResponder类

UIResponder父类是NSObject

// 下一个接收事件的响应者
- (nullable UIResponder*)nextResponder;
// 是否能成为第一响应者,默认NO,和becomeFirstResponder配合使用
- (BOOL)canBecomeFirstResponder;
// 是否成为第一响应者,和canBecomeFirstResponder配合使用
- (BOOL)becomeFirstResponder;
// 是否能取消第一响应者,默认YES,和resignFirstResponder配合使用
- (BOOL)canResignFirstResponder;
// 是否取消第一响应者,和canResignFirstResponder配合使用
- (BOOL)resignFirstResponder;
// 判断是否是第一响应者
- (BOOL)isFirstResponder;

// 触摸API,一般用于有触摸屏的设备
// 通知调用者当有一根或者多根手指触摸到了视图或者窗口时调用
// 必须把YES传入当前视图实例的setMultipleTouchEnabled消息,才会接收多点触摸事件
- (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:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

// 深按API,一般用于遥控器
// 开始按压的时候调用
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
// 按压改变的时候调用
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
// 按压结束的时候调用
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
// 当系统发出取消按压事件的时候调用
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

// 加速计API,一般用于可以产生加速计事件的设备,如微信的摇一摇功能
// 开始加速的时候调用
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
// 加速结束的时候调用
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
// 系统发出取消加速计事件的时候调用
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

// 远程控制API,一般用于耳机
// 接收到远程控制消息时执行
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);

四. UITouch类

UITouch父类是NSObject
注意:
1. 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象,一根手指对应一个UITouch对象,保存着跟手指相关的信息,比如触摸的位置、时间、阶段
2. 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
3. 当手指离开屏幕时,系统会销毁相应的UITouch对象
4. iPhone开发中,要避免使用双击事件

// 记录了触摸事件产生或变化时的时间,单位是秒,NSTimeInterval实质是double
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;
// 短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;
// 触摸类型
@property(nonatomic,readonly) UITouchType type NS_AVAILABLE_IOS(9_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);

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

// 该方法记录了前一个触摸点的位置
// 表示触摸在view这个视图上的位置,这里返回的位置是针对view的坐标系的
// 调用时传入的view参数为空的话,返回的是触摸点在整个窗口的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;

五. UIEvent类

UIEvent父类是NSObject
注意:
1. 每产生一个触摸事件,就会产生一个UIEvent对象
2. 产生一个事件,不一定就会产生UIEvent对象,因为按压事件对应UIPressesEvent对象,加速事件对应UIEvent对象,远程控制事件对应UIEvent对象
3. UIPressesEvent类是UIEvent的子类

// 事件类型
@property(nonatomic,readonly) UIEventType     type NS_AVAILABLE_IOS(3_0);
@property(nonatomic,readonly) UIEventSubtype  subtype NS_AVAILABLE_IOS(3_0);
// 事件产生的时间
@property(nonatomic,readonly) NSTimeInterval  timestamp;
// 获取产生事件的触摸对象集
- (nullable NSSet <UITouch *> *)allTouches;
// 获取在window上产生的触摸对象集
- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;
// 获取在view上产生的触摸对象集
- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
// 获取有手势gesture的触摸对象集
- (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture NS_AVAILABLE_IOS(3_2);

六. 事件的产生和传递的底层处理过程

当用户与iPhone的触摸屏产生互动时,硬件就会探测到物理接触并且通知操作系统,接着操作系统就会创建相应的事件并且将其传递给当前正在运行的应用程序的事件队列,然后这项事件会被事件循环传递给优先响应者,优先响应者是事件被触发时和用户交互的控件,比如按钮、视图等,如果我们编写了代码让优先响应者处理这种类型的事件,那么它就会处理这种类型的事件,如:如果是触摸事件,重写四个触摸事件处理方法,系统会自动执行这些方法。

处理完某项事件后,响应者有两个选项:一是将其丢弃;二是将其传递给响应链条中的下一个响应者。下一个响应者的地址存储在当前响应者所包含的变量nextResponder当中。如果优先响应者无法处理一项事件,那么这项事件就传递给下一个响应者,直到这项事件到达能处理它的响应者或者到达响应链条的末端,也就是UIApplication对象。UIApplication对象收到一项事件后,也是要么处理,要么丢弃。

具体分析:
1.发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序的主窗口(keyWindow),主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步,找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理。
提示: 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件。

2.如何找到最合适的控件来处理事件?
(1). 判断自己是否能接收触摸事件(不能接收事件情况:不接收用户交互、隐藏、透明度为0.0 ~ 0.01)
提示: UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的
(2). 判断触摸点是否在自己身上
(3). 从后往前遍历子控件,重复前面的两个步骤
(4). 如果没有找到符合条件的子控件,那么就认为自己是最适合的控件

// hitTest: withEvent:方法是返回最合适的view
// 自己实现hitTest: withEvent:方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    // 1. 判断能不能处理该方法
    if (self.userInteractionEnabled == NO || self.alpha <=0.01 || self.hidden == YES) return nil;

    // 2. 判断点在不在自己上面
    if ([self pointInside:point withEvent:event] == NO) return nil;

    // 3. 遍历子控件,判断是否有比自己更合适处理该事件的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        // 获取子控件
        UIView *childView = self.subviews[i];

        // 转换点
        CGPoint childPoint = [self convertPoint:point toView:childView];

        // 递归判断
        UIView *view = [childView hitTest:childPoint withEvent:event];

        // 如果子控件更合适处理事件,就返回子控件
        if (view) return view; 
    }

    // 4. 没有比自己更合适处理该事件的子控件,就返回自己
    return self;
}

七. 完整的触摸过程

1.一次完整的触摸过程,会经历3个状态:

触摸开始:- (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:(nullable NSSet < UITouch * > * )touches withEvent:(nullable UIEvent *)event;

2.四个触摸事件处理方法中,都有(NSSet < UITouch * > * )touches和(nullable UIEvent * )event两个参数,在一次完整的触摸过程中,只会产生一个事件对象,四个触摸方法都是同一个event参数

3.如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象

4.如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象

5.根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸

6.如果没有重写这些touches方法,默认做法是将事件顺着响应者链条向上传递,将事件交给上一个(往底层方向)响应者进行处理

7.在重写touches方法时,如果调用了[super touches…];就会将事件顺着响应者链条往上(往底层方向)传递,传递给上一个响应者

八. 响应者链条

1.响应者链条:是由多个响应者对象连接起来的链条

2.响应者链条作用:能很清楚的看见每个响应者之间的联系,并且可以让一个事件多个对象处理

3.响应者对象:能处理事件的对象,如 :Application 、window 、view controller、view、child view

4.如何判断上一个(往底层方向)响应者
(1). 如果当前这个view是控制器的view,那么控制器就是上一个响应者
(2). 如果当前这个view不是控制器的view,那么其父控件就是上一个响应者

5.响应者链的事件传递过程
(1). 如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图
(2). 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
(3). 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
(4). 如果UIApplication也不能处理该事件或消息,则将其丢弃

九. 补充

1.寻找最合适的响应者是从最底层往上找的,如 window —> view —> child View

2.响应者链是从最上层往底层传递的,如 child view —> view —> view controller —> window —> Application

3.监听一个view上面的触摸事件
(1). 自定义一个view
(2). 实现view的touches方法,在方法内部实现具体处理代码

4.storyboard上的控件,在没有设置控件的class时不能从storyboard往代码上拖线,但是通过手写代码加上IBOutlet或Action,就可以从代码向storyboard上拖线

5.关于事件的注意点
(1). 系统事件就交给代理处理,不是就交给窗口处理
(2). 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
(3). 如果父控件隐藏了,它的子控件也看不到了
(4). 如果设置父控件的透明度,子控件也会受到影响
(5). 调试bug时,当一个控件不能接收事件,看看他的父控件能不能接收事件
(6). 子控件超出父控件是可以显示的,但超出的部分一般不做事件处理

6.写代码的时候按功能抽取方法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值