拾遗系列(七)iOS中的事件

iOS中的事件

响应者对象

只有继承了UIResponder的对象才能接收并处理事件,称为响应者对象

  • 如:UIApplication、UIViewController、UIView

UIView不接收触摸事件的三种情况

  • 1.不接收用户交互

    userInteractionEnabled = NO

  • 2.隐藏

    hidden = YES

  • 3.透明

    alpha = 0.0 ~ 0.01

提示:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的

一、UIResponder事件处理

iOS中的事件可以分为3大类型

1. 触摸事件

//手指开始触摸
- (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;

touches中存放的都是UITouch对象

UITouch对象

当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象,一根手指对应一个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;
  • UITouch方法

    触摸点在view上的位置(针对view的坐标系的),view为nil则返回在UIWindow的位置
    - (CGPoint)locationInView:(UIView *)view;   
    记录了前一个触摸点的位置
    - (CGPoint)previousLocationInView:(UIView *)view;
    

UIEvent

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

  • 常见属性
事件类型
@property(nonatomic,readonly) UIEventType     type;
@property(nonatomic,readonly) UIEventSubtype  subtype;

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

UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)

说明

  • 4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数
  • 一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数

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

    • 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
  • 根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸

2. 加速计事件(摇晃)

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

3. 远程控制事件

- (void)remoteControlReceivedWithEvent:(UIEvent *)event;

二、事件的产生和传递(原理)

产生与传递

  • 1.发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中

  • 2.UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)

  • 3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步

  • 4 找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
    touchesBegan、touchesMoved、touchedEnded等

如何找到最合适的视图

说明:

  • 触摸事件的传递是从父控件传递到子控件
  • 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件

实例:
拾遗系列(七)iOS中的事件_第1张图片

点击黄色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色 -> 黄色

如何从白色UIView找到黄色UIView

  • 1.自己是否能接收触摸事件?hinTest
  • 2.触摸点是否在自己身上?pointInside
  • 3.从后往前遍历子控件,重复前面的两个步骤。(先遍历后添加的子控件)
  • 4.如果没有符合条件的子控件,那么自己就是最适合处理

找到最合适的view所调用方法:hitTest、pointInside

hitTest

事件传递的时候调用,当事件传递给控件的时候,就会调用控件的这个方法,去寻找最合适的view

// 作用:寻找最合适的view
// point:当前的触摸点,point这个点的坐标系就是方法调用者
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //point指的是调用者的坐标系,如果当前是UIWindow调用此方法,那么坐标系就是当前屏幕
    //调用系统的做法去寻找最合适的view,返回最合适的view
    UIView *fitView = [super hitTest:point withEvent:event];
    return fitView;
}

pointInside

// 作用:判断当前这个点在不在方法调用者(控件)上
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return YES;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//    NSLog(@"%s",__func__);
}
hitTest底层实现
// UIApplication -> [UIWindow hitTest:withEvent:] -> whiteView hitTest:withEvent
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //系统调用方法
    //NSLog(@"%@--hitTest",[self class]);
    //return [super hitTest:point withEvent:event];

    //自己实现方法
    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) 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 childP = [self convertPoint:point toView:childView];
       UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) { // 寻找到最合适的view
            return fitView;
        }
    }
    // 循环结束,表示没有比自己更合适的view
    return self;  
}

坐标系转换

 self convertPoint:(CGPoint) toView:(nullable UIView *)

触摸事件处理的详细过程

  • 1 用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件

  • 2 找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理

    • touchesBegan…
    • touchesMoved…
    • touchedEnded…
  • 3 这些touches方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理

响应者链条

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

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

完整过程

  • 1.先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。

  • 2.调用最合适控件的touches….方法

  • 3.如果调用了[super touches….],就会将事件顺着响应者链条往上传递,传递给上一个响应者

  • 4.接着就会调用上一个响应者的touches….方法

    • 如何判断上一个响应者
      • 如果当前这个view是控制器的view,那么控制器就是上一个响应者
      • 如果当前这个view不是控制器的view,那么父控件就是上一个响应者
      • 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
      • 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
      • 如果UIApplication也不能处理该事件或消息,则将其丢弃

如何监听触摸事件

旧的做法

  • 自定义一个view
  • 实现view的touches方法,在方法内部实现具体处理代码

通过touches方法监听view触摸事件,有很明显的几个缺点

  • 必须得自定义view
  • 由于是在view内部的touches方法中监听触摸事件,因此默认情况下,无法让其他外界对象监听view的触摸事件
  • 不容易区分用户的具体手势行为

手势识别(UIGestureRecognizer)

利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势

UIGestureRecognizer 是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势

  • UITapGestureRecognizer(敲击)
  • UIPinchGestureRecognizer(捏合,用于缩放)
  • UIPanGestureRecognizer(拖拽)
  • UISwipeGestureRecognizer(轻扫)
  • UIRotationGestureRecognizer(旋转)
  • UILongPressGestureRecognizer(长按)

手势使用

  • 创建手势识别器对象

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]   init];
  • 设置手势识别器对象的具体属性

    “`obj-c
    // 连续敲击2次
    tap.numberOfTapsRequired = 2;
    // 需要2根手指一起敲击
    tap.numberOfTouchesRequired = 2;


- 添加手势识别器到对应的view上

    ```obj-c
[self.iconView addGestureRecognizer:tap];
  • 监听手势的触发

    “`obj-c
    [tap addTarget:self action:@selector(tapIconView:)];


####手势状态

```obj-c
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    // 没有触摸事件发生,所有手势识别的默认状态
    UIGestureRecognizerStatePossible,
    // 一个手势已经开始但尚未改变或者完成时
    UIGestureRecognizerStateBegan,
    // 手势状态改变
    UIGestureRecognizerStateChanged,
    // 手势完成
    UIGestureRecognizerStateEnded,
    // 手势取消,恢复至Possible状态
    UIGestureRecognizerStateCancelled, 
    // 手势失败,恢复至Possible状态
    UIGestureRecognizerStateFailed,
    // 识别到手势识别
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};




<div class="se-preview-section-delimiter">div>

点按手势

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tap.delegate = self;
[_imageView addGestureRecognizer:tap];




class="se-preview-section-delimiter">

UIGestureRecognizer**也有代理**

长按手势

// 默认会触发两次
- (void)setUpLongPress
{
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
    [self.imageView addGestureRecognizer:longPress];
}

- (void)longPress:(UILongPressGestureRecognizer *)longPress
{
    if (longPress.state == UIGestureRecognizerStateBegan) {
        NSLog(@"%s",__func__);
    }
}




class="se-preview-section-delimiter">

清扫

- (void)setUpSwipe
{
    // 默认轻扫的方向是往右
    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe)];
    swipe.direction = UISwipeGestureRecognizerDirectionUp; 
    [self.imageView addGestureRecognizer:swipe];

    // 如果以后想要一个控件支持多个方向的轻扫,必须创建多个轻扫手势,一个轻扫手势只支持一个方向
    // 默认轻扫的方向是往右
    UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe)];
    swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
    [self.imageView addGestureRecognizer:swipeDown]; 
}

- (void)swipe
{
    NSLog(@"%s",__func__);
}




class="se-preview-section-delimiter">

旋转手势

- (void)setUpRotation
{
    UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
    rotation.delegate = self;
    [self.imageView addGestureRecognizer:rotation];
}

// 默认传递的旋转的角度都是相对于最开始的位置
- (void)rotation:(UIRotationGestureRecognizer *)rotation
{
    self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation.rotation);

    // 复位
    rotation.rotation = 0;
    // 获取手势旋转的角度
    NSLog(@"%f",rotation.rotation);
}




"se-preview-section-delimiter">

捏合

- (void)setUpPinch
{
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
    pinch.delegate = self;
    [self.imageView addGestureRecognizer:pinch];
}

- (void)pinch:(UIPinchGestureRecognizer *)pinch
{
    self.imageView.transform = CGAffineTransformScale(self.imageView.transform, pinch.scale, pinch.scale);

    // 复位
    pinch.scale = 1;
}




"se-preview-section-delimiter">

拖拽

- (void)setUpPan
{
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.imageView addGestureRecognizer:pan];
}

- (void)pan:(UIPanGestureRecognizer *)pan
{
    // 获取手势的触摸点
    // CGPoint curP = [pan locationInView:self.imageView];

    // 移动视图
    // 获取手势的移动,也是相对于最开始的位置
    CGPoint transP = [pan translationInView:self.imageView];
    self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, transP.x, transP.y);

    // 复位
    [pan setTranslation:CGPointZero inView:self.imageView];
}

你可能感兴趣的:(ios,ios,事件)