IOS基础和进阶开发selector

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

2016-11-18  本文已影响53人  王大吉Rock

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的事件方法才能够监听事件。

参数说明:

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. 事件传递过程中,需要寻找到最适合的View来执行事件。

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

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

作用:寻找最适合的View
参数:当前手指所在的点.产生的事件
返回值:返回谁, 谁就是最适合的View.
什么时候用调用:只要一个事件,传递给一个控件时, 就会调用这个控件的hitTest方法
    -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
作用:判断point在不在方法调用者上
point:必须是方法调用者的坐标系
什么时候调用:hitTest方法底层会调用这个方法,判断点在不在控件上.
    -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
        return YES;
    }
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;
    }
上一篇下一篇

猜你喜欢

热点阅读