美文网首页IOS基础和进阶开发selector
触摸事件、事件传递(响应者、响应者链)、手势

触摸事件、事件传递(响应者、响应者链)、手势

作者: 王大吉Rock | 来源:发表于2016-11-18 15:40 被阅读53次

    UITouch官方教程
    UIGesturereCognizer官方教程

    参考教程

    事件处理demo

    IOS当中常用的事件分为三种:

    • 触摸事件
    • 加速计事件
    • 远程控制事件

    触摸事件

    什么是响应者对象?

    继承了UIResponds的对象我们称它为响应者对象UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。

    为什么说继承了UIResponder就能够处理事件?

    因为UIResponder内部提供了以下方法来处理事件

        触摸事件会调用以下方法:
        - (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;
        加速计事件会调用:
        - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
        - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
        - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
        远程控制事件会调用:
        - (void)remoteControlReceivedWithEvent:(UIEvent *)event;
    

    如何监听UIView的触摸事件

    想要监听UIViiew的触摸事件,首先第一步要自定义UIView,因为只有实现了UIResponder的事件方法才能够监听事件。

    • UIView的触摸事件主要有:
      一根或者多根手指开始触摸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
      

    参数说明:

    touches:

    (1)touches中存放的都是UITouch对象,它是一个NSSet集合。
    (2)UITouch对象它就是用来保存手指相关联的信息。包括位置、时间、阶段等信息.
    (3)每一个手指对应着一个UITouch对象。
    (4)这个UITouch是系统自动帮我们创建的,当手指移动时,系统会更新同一个UITouch对象,使它能够一直保存该手指在的触摸位置.
    (5)通过获取UITouch属性,我们可以获得触摸产生时所处的窗口、触摸的View、时间、点击的次数等,这些都可以在通过UITouch获取。
    (6)通过UITouch提供的方法获取当前手指所在的点,以及上一个手指所在的点。

    // 取当前手指所在的点
    - (CGPoint)locationInView:(UIView *)view;
    
    // 获取上一个触摸点的位置.
    - (CGPoint)previousLocationInView:(UIView *)view;
    
    event:
    1. 每产生一个事件,就会产生一个UIEvent对象
    2. UIEvent:称为事件对象,记录事件产生的时刻和类型
    3. 一次完整的触摸过程,会经历3个状态:
    触摸开始:- (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个触摸方法都是同一个event参数。如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象。如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象。

    事件的产生和传递

    1. 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。
    2. UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
    3. 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
    4. 找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
      touchesBegan…
      touchesMoved…
      touchedEnded…

    具体说个例子:


    事件传递View.png

    触摸事件的传递是从父控件传递到子控件
    点击了绿色的view:
    UIApplication -> UIWindow -> 白色 -> 绿色
    点击了蓝色的view:
    UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色
    点击了黄色的view:
    UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色 -> 黄色

    事件传递的完整过程

    大概可以理解为:用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件,找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理,那这些touches方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理。

    响应者链条示意图.png
    1. 事件传递的完整过程:
      (1) 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。
      (2)调用最合适控件的touches….方法
      (3)如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者
      (4)接着就会调用上一个响应者的touches….方法

    2. 寻找上一个响应者 (如图响应者链条图.png)
      (1)如果当前的View是控制器的View,那么控制器就是上一个响应者.
      (2)如果当前的View不是控制器的View,那么它的父控件就是上一个响应者.
      (3)在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
      (4)如果window对象也不处理,则其将事件或消息传递给UIApplication对象
      (5)如果UIApplication也不能处理该事件或消息,则将其丢弃

    关于事件相关的注意点:
    • 一个控件什么情况下不能够接收事件.
      1.不接收用户交互时不能够处理事件
      userInteractionEnabled = NO
      2.当一个控件隐藏的时候不能够接收事件
      Hidden = YES的时候
      3.当一个控件为透明白时候也不能够接收事件
      注意:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的。
    1. 事件传递过程中,需要寻找到最适合的View来执行事件。

    主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件.

    那如何找到最合适的View呢?
    1.先判断自己是否能够接收触摸事件,如果能再继续往下判断,
    2.再判断触摸的当前点在不在自己的身上.
    3.如果在自己身上,它会从后往前遍历子控件,遍历出每一个子控件后,重复前面的两个步骤.
    4.如果没有符合条件的子控件,那么它自己就是最适合的View.

    • hitTest方法与PointInside方法
    作用:寻找最适合的View
    参数:当前手指所在的点.产生的事件
    返回值:返回谁, 谁就是最适合的View.
    什么时候用调用:只要一个事件,传递给一个控件时, 就会调用这个控件的hitTest方法
        -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    
    作用:判断point在不在方法调用者上
    point:必须是方法调用者的坐标系
    什么时候调用:hitTest方法底层会调用这个方法,判断点在不在控件上.
        -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
            return YES;
        }
    
    • hitTest底层实现:
    1.判断当前能不能接收事件
     if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) 
    
    2.判断触摸点在不在当前的控件上
    [self pointInside:point withEvent:event];
    
    3.从后往前遍历自己的子控件
    int count = (int)self.subviews.count;
    for (int i = count - 1; i >= 0;i-- ) {
            UIView *childV = self.subviews[i];
            // 把当前坐标系上的点转换成子控件坐标系上的点.
            CGPoint childP = [self convertPoint:point toView:childV];
            // 判断自己的子控件是不是最适合的View
            UIView *fitView = [childV hitTest:childP withEvent:event];
            // 如果子控件是最适拿的View,直接返回
                if (fitView) {
                    return  fitView;
                }
            }
    
    4.自己就是最适合的View
    
    

    UIGestureRecognizer手势

    通过touches方法监听view触摸事件有以下几个缺点
    1.必须得自定义view,在自定义的View当中去实现touches方法.
    2.由于是在view内部的touches方法中监听触摸事件,因此默认情况下,无法让其他外界对象监听view的触摸事件
    3.不容易区分用户的具体手势行为(不容易区分是长按手势,还是缩放手势)这些等.

    iOS 3.2之后,苹果推出了手势识别功能UIGestureRecognizer

    利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势
    UIGestureRecognizer是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势
    
    注意手势有以下几种:
    UITapGestureRecognizer(敲击)
    UIPinchGestureRecognizer(捏合,用于缩放)
    UIPanGestureRecognizer(拖拽)
    UISwipeGestureRecognizer(轻扫)
    UIRotationGestureRecognizer(旋转)
    UILongPressGestureRecognizer(长按)
    
    手势识别的基本属性:

    手势识别的状态

    typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
        // 没有触摸事件发生,所有手势识别的默认状态
        UIGestureRecognizerStatePossible,
        // 一个手势已经开始但尚未改变或者完成时
        UIGestureRecognizerStateBegan,
        // 手势状态改变
        UIGestureRecognizerStateChanged,
        // 手势完成
        UIGestureRecognizerStateEnded,
        // 手势取消,恢复至Possible状态
        UIGestureRecognizerStateCancelled, 
        // 手势失败,恢复至Possible状态
        UIGestureRecognizerStateFailed,
        // 识别到手势识别
        UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
    };
    
    手势的使用:
    1. 添加点按手势
      创建手势
      Target:当哪对象要坚听手势
      action:手势发生时调用的方法
      UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
      action:@selector(tap)];
      手势也可以设置代理
      tap.delegate = self;
      添加手势
      [self.imageV addGestureRecognizer:tap];

       以下为手势代理方法:
       是否允许接收手指.
       当返回为yes的时候,表示能够接收手指,当为No的时候,表示不能够接收手指,也就是不能够接收事件.
       -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:
       (UITouch *)touch{
           获取当前手指所在的点
          CGPoint curP =  [touch locationInView:self.imageV];
           if (curP.x > self.imageV.bounds.size.width * 0.5) {
               在右边,返回NO
               return NO;
           }else{
                在左边,返回yes,
               return YES;
           }
       }
       
       当手指开始点击时调用
       -(void)tap{
           NSLog(@"%s",__func__);
       }
      

      2.添加长按手势
      UILongPressGestureRecognizer *longP = [[UILongPressGestureRecognizer alloc]
      initWithTarget:self action:@selector(longPress:)];
      添加手势
      [self.imageV addGestureRecognizer:longP];

       当手指长按时调用
       注意,长按手势会调用多次,当开始长按时会调用,当长按松开时会调用,当长按移动时, 也会调用.
       一般我们都是在长按刚开始时做事情,所以要判断它的状态.
       这个状态是保存的当前的手势当中, 所以要把当前的长按手势传进来, 来判断当前手势的状态.
       
       - (void)longPress:(UILongPressGestureRecognizer *)longP{
           手势的状态是一个枚举UIGestureRecognizerState,可以进入头文件当中查看.
           if (longP.state == UIGestureRecognizerStateBegan) {
               NSLog(@"开始长按时调用");
           }else if(longP.state == UIGestureRecognizerStateChanged){
               会持续调用
               NSLog(@"当长按拖动时调用");
           }else if(longP.state == UIGestureRecognizerStateEnded){
               NSLog(@"当长按松手指松开进调用");
           }
           
       }
      

      3.轻扫手势

       UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] 
                               initWithTarget:self action:@selector(swipe:)];
       注意:轻扫手势默认轻扫的方向是往右轻扫,可以去手动修改轻扫的方向
       一个手势只能对象一个方向,想要支持多个方向的轻扫,要添加多个轻扫手势
       swipe.direction = UISwipeGestureRecognizerDirectionLeft;
       添加手势
       [self.imageV addGestureRecognizer:swipe];
       
       再添加一个轻扫手势
       轻扫手势
       UISwipeGestureRecognizer *swipe2 = [[UISwipeGestureRecognizer alloc] 
                               initWithTarget:self action:@selector(swipe:)];
       注意:轻扫手势默认轻扫的方向是往右轻扫,可以去手动修改轻扫的方向
       一个手势只能对象一个方向,想要支持多个方向的轻扫,要添加多个轻扫手势
       swipe2.direction = UISwipeGestureRecognizerDirectionDown;
       添加手势
       [self.imageV addGestureRecognizer:swipe2];
      

    4.拖动
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self
    action:@selector(pan:)];
    添加手势
    [self.imageV addGestureRecognizer:pan];

          实现手势方法
           手指在屏幕上移动进调用
            - (void)pan:(UIPanGestureRecognizer *)pan{
                获取当前手指移动的偏移量
                CGPoint transP =  [pan translationInView:self.imageV];
                NSLog(@"%@",NSStringFromCGPoint(transP));
                Make它会清空上一次的形变.
                self.imageV.transform = CGAffineTransformMakeTranslation(transP.x, transP.y);
                
                self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, 
                                                                           transP.x, transP.y);
                复位,相对于上一次.
                [pan  setTranslation:CGPointZero inView:self.imageV];
            }
    
    2.旋转
          
        添加旋转手势
        UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] 
                                     initWithTarget:self action:@selector(rotation:)];
        设置代理,设置代理的目的就让它能够同时支持旋转跟缩放
        rotation.delegate = self;
        添加手势
        [self.imageV addGestureRecognizer:rotation];
        当旋转时调用
        - (void)rotation:(UIRotationGestureRecognizer *)rotation{
            旋转也是相对于上一次
            self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, 
                                                                 rotation.rotation);
            设置代理,设置代理的目的就让它能够同时支持旋转跟缩放
            rotation.delegate = self;
            也要做复位操作
            rotation.rotation = 0;
        }
    
    3.添加缩放手势
        添加缩放手势
        UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]                                             initWithTarget:self action:@selector(pinch:)];
        
       [self.imageV addGestureRecognizer:pinch];
        缩放手势时调用
        -(void)pinch:(UIPinchGestureRecognizer *)pinch{
            平移也是相对于上一次
            self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pinch.scale, 
                                                                                  pinch.scale);
            复位
            pinch.scale = 1;
        }

    相关文章

      网友评论

        本文标题:触摸事件、事件传递(响应者、响应者链)、手势

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