:多点触摸与手势检测

Posted xiaoxiaobukuang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:多点触摸与手势检测相关的知识,希望对你有一定的参考价值。

一、响应者链

只要继承了UIResponder的对象就可作为事件的响应者,实际上UIControl继承了UIView,UIView又继承了UIResponder,由此可见,所有的对象都可作为事件的响应者。

当用户与某个控件交互时,该控件将作为“第一响应者(First Responder)”,第一响应者将作为响应者链的开始,该事件首先被发送给第一响应者(也就是用户触摸屏幕的控件)。事件将沿着响应者链一直向下传播,直到被某个响应者处理。事件响应者链的典型传播路线如下:

First Responder —> First Responder的视图控制器(如果有)—>父容器(如果有)—>父容器的视图控制器(如果有)—>UIWindow—>UIApplication—>应用程序委托对象

如果某个响应者“截获”了某个事件,那么该响应者要根据条件决定是否处理该事件。当响应者无法处理该事件时,则需要在处理方法中手动传递该事件。例如如下代码:

- (void)handleTapEvent:(UITapGestureRecognizer *)event{
    if (YES) {
        //处理事件
    }else{
        OtherTapView* view = (OtherTapView *)self.nextResponder;
        [view handleTapEvent:event];
    }
}

二、响应触碰方法

如果希望自定义控件可以响应用户的触碰事件,则可以通过通过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)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
     NSLog(@"%ld",[touches count]);
     NSLog(@"%ld",[[touches anyObject]tapCount]);
     CGPoint point = [[touches anyObject] locationInView:self.view];
}

注意:

  • ①、第一个NSSet类型的参数代表了用户同时触碰控件的多个手指,如果该用户用3个手指触碰该控件,那么该touches参数中将会包含3个UITouch对象;
  • ②、UITouch对象代表一个触碰事件,该对象提供了一个tapCount属性,该属性用于返回用户触碰屏幕的次数,比如用户只是轻轻的触碰屏幕一次,那么该属性将返回1.如果该属性返回3,则代表用户快速触碰了3次屏幕.
  • ③、UITouch还提供了locationInView:方法来获取该触碰事件在UIView控件中的碰触位置.

三、使用手势处理器(UIGestureRecognizer)

UIGestureRecognizer提供了如下子类:

  • ①、UITapGestureRecognizer:处理用户点击手势的手势处理器;
  • ②、UIPinchGestureRecognizer:处理用户捏合手势的手势处理器;
  • ③、UIRotationGestureRecognizer:处理用户旋转手势的手势处理器;
  • ④、UISwipeGestureRecognizer:处理用户滑动手势的手势处理器;
  • ⑤、UIPanGestureRecognizer:处理用户拖动手势的手势处理器;
  • ⑥、UILongPressGestureRecognizer:处理用户长按手势的手势处理器;

使用手势处理器处理用户触碰手势的编程步骤如下:

  1. 、根据程序要处理的手势创建对象的手势处理器对象。创建手势处理器时需要制定target和action参数—当该控件上发生触碰手势后,该target对象的action方法将会被激发。
  2. 、如果该UI控件不允许交互,则将该UI控件的userInteractionEnable属性设为YES;如果希望该控件可支持多点触碰,还需要将multipleTouchEnable设为YES.
  3. 、调用UI控件的addGestureRecognizer:方法添加该手势处理器;

UIGestureRecognizer作为所有手势处理器的基类,它提供了如下常用的方法和属性:

  • (1)、- (CGPoint)locationInView:(UIView*)view;返回该手势在view控件中的触碰位置;
  • (2)、- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView*)view;返回该手势中第touchIndex个触碰点在view控件中的位置;
  • (3)、- (NSUInteger)numberOfTouches;返回该手势包含触碰点的数量(也就是用户用了几个手指进行触碰);
  • (4)、@property(nonatomic,readonly) UIView *view;返回激发该手势的UI控件;
  • (5)、@property(nonatomic, getter=isEnabled) BOOL enabled;用户设置和返回该手势处理器是否可用;
  • (6)、@property(nonatomic,readonly) UIGestureRecognizerState state;获取该手势所处的状态,该状态分为如下几种:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    UIGestureRecognizerStatePossible,
    UIGestureRecognizerStateBegan,
    UIGestureRecognizerStateChanged,
    UIGestureRecognizerStateEnded,
    UIGestureRecognizerStateCancelled,
    UIGestureRecognizerStateFailed,
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};

1、使用UITapGestureRecognizer处理点击事件

UITapGestureRecognizer还提供了如下两个属性:

  • (1)、@property (nonatomic) NSUInteger numberOfTapsRequired;指定该手势处理器只处理几次触碰事件;
  • (2)、@property (nonatomic) NSUInteger numberOfTouchesRequired;指定该手势处理器只处理几个手指的触碰事件.

实例:

- (void)viewDidLoad {
    [super viewDidLoad];
    gv = [[UIView alloc]initWithFrame:CGRectMake(10, 200, self.view.frame.size.width - 20, 200)];
    gv.layer.borderWidth = 2;
    gv.layer.cornerRadius = 6;
    //设置gv控件支持用户交互
    gv.userInteractionEnabled = YES;
    //设置gv控件支持多点触碰
    gv.multipleTouchEnabled = YES;
    [self.view addSubview:gv];
    label = [[UILabel alloc]initWithFrame:CGRectMake(20, 50, gv.frame.size.width - 40, 40)];
    [gv addSubview:label];
    self.view.backgroundColor = [UIColor lightGrayColor];
    for (int i = 0; i < 6; i ++) {
        //创建手势处理器,指定使用该控制器的handleTap:方法处理手势
        UITapGestureRecognizer* gesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleTap:)];
        //设置该点击手势处理器只处理i次连击事件
        gesture.numberOfTapsRequired = i;
        //设置该点击手势处理器只处理两个手指的触碰事件
        gesture.numberOfTouchesRequired = 2;
        //为gv控件添加手势处理器
        [gv addGestureRecognizer:gesture];
    }
}
- (void)handleTap:(UITapGestureRecognizer *)gesture{
    NSInteger touchNum = gesture.numberOfTouches;
    NSUInteger tapNum = gesture.numberOfTapsRequired;
    label.text = [NSString stringWithFormat:@"用户使用%ld个手指进行触碰,触碰次数为:%ld",touchNum,tapNum];
    //指定2秒后清除label的文本
    [label performSelector:@selector(setText:) withObject:@"" afterDelay:5];
}

2、使用UIPinchGestureRecognizer处理捏合事件

UIPinchGestureRecognizer定义了如下两个属性来获取捏合相关信息:

  • (1)、@property (nonatomic) CGFloat scale;获取捏合的比例;
  • (2)、@property (nonatomic,readonly) CGFloat velocity;获取捏合的速度;

实例:

- (void)viewDidLoad {
    [super viewDidLoad];
    gv = [[UIView alloc]initWithFrame:CGRectMake(10, 200, self.view.frame.size.width - 20, 200)];
    gv.layer.borderWidth = 2;
    gv.layer.cornerRadius = 6;
    //设置gv控件支持用户交互
    gv.userInteractionEnabled = YES;
    //设置gv控件支持多点触碰
    gv.multipleTouchEnabled = YES;
    [self.view addSubview:gv];
    label = [[UILabel alloc]initWithFrame:CGRectMake(20, 50, gv.frame.size.width - 40, 80)];
    label.numberOfLines = 2;
    [gv addSubview:label];
    self.view.backgroundColor = [UIColor lightGrayColor];
    UIPinchGestureRecognizer* gesture = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
    //为gv控件添加手势处理器
    [gv addGestureRecognizer:gesture];
}
- (void)handlePinch:(UIPinchGestureRecognizer *)gesture{
    //获取用户捏合的速度和比例
    CGFloat velocity = gesture.velocity;
    CGFloat scale = gesture.scale;
    label.text = [NSString stringWithFormat:@"用户捏合的速度为:%g,比例为:%g",velocity,scale];
    //指定2秒后清除label的文本
    [label performSelector:@selector(setText:) withObject:@"" afterDelay:5];
}

实例:通过捏合手势缩放图片

- (void)viewDidLoad {
    [super viewDidLoad];
    [UIApplication sharedApplication].statusBarHidden = YES;
    srcImage = [UIImage imageNamed:@"妞.png"];
    //设置图片直接显示在中间(不进行任何缩放)
    self.view.contentMode = UIViewContentModeCenter;
    self.view.backgroundColor = [UIColor lightGrayColor];
    gv = [[UIImageView alloc]initWithFrame:CGRectMake(10, 200, self.view.frame.size.width - 20, 200)];
    currentSize = CGSizeMake(self.view.frame.size.width - 20, 200);
    gv.layer.borderWidth = 2;
    gv.layer.cornerRadius = 6;
    //设置gv控件支持用户交互
    gv.userInteractionEnabled = YES;
    //设置gv控件支持多点触碰
    gv.multipleTouchEnabled = YES;
    [self.view addSubview:gv];
    gv.image = srcImage;
    //创建UIPinchGestureRecognizer手势处理器,该手势处理器激发scaleImage:方法
    UIPinchGestureRecognizer* gesture = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(scaleImage:)];
    [gv addGestureRecognizer:gesture];
}
//imageView进行缩放
- (void)scaleImage:(UIPinchGestureRecognizer *)gesture{
    gv.bounds = CGRectMake(0, 0, currentSize.width * gesture.scale, currentSize.height * gesture.scale);
    if (gesture.state == UIGestureRecognizerStateEnded) {
        currentSize = CGSizeMake(currentSize.width * gesture.scale, currentSize.height * gesture.scale);
    }
}

3、使用UIRotationGestureRecognizer处理旋转手势

UIRotationGestureRecognizer定义了如下两个属性来获取旋转相关信息:

  • (1)、@property (nonatomic)CGFloat rotation;获取旋转角度;
  • (2)、@property (nonatomic,readonly) CGFloat velocity;获取旋转速度;

实例:通过旋转手势旋转图片

- (void)viewDidLoad {
    [super viewDidLoad];
    srcImage = [UIImage imageNamed:@"妞.png"];
    //设置图片直接显示在中间(不进行任何缩放)
    self.view.backgroundColor = [UIColor lightGrayColor];
    gv = [[UIImageView alloc]initWithFrame:CGRectMake(10, 200, self.view.frame.size.width - 20, 200)];
    currentSize = CGSizeMake(self.view.frame.size.width - 20, 200);
    gv.layer.borderWidth = 2;
    gv.layer.cornerRadius = 6;
    //设置gv控件支持用户交互
    gv.userInteractionEnabled = YES;
    //设置gv控件支持多点触碰
    gv.multipleTouchEnabled = YES;
    [self.view addSubview:gv];
    gv.image = srcImage;
    //创建UIPinchGestureRecognizer手势处理器,该手势处理器激发scaleImage:方法
    UIRotationGestureRecognizer* gesture = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotateImage:)];
    [gv addGestureRecognizer:gesture];
}
- (void)rotateImage:(UIRotationGestureRecognizer *)gesture{
    //根据当前缩放的比例计算缩放后的目标图片大小
    gv.transform = CGAffineTransformMakeRotation(imageRotate + gesture.rotation);
    if (gesture.state == UIGestureRecognizerStateEnded) {
        imageRotate += gesture.rotation;
    }
}

4、使用UISwipeGestureRecognizer处理轻扫手势

UISwipeGestureRecognizer定义了如下两个属性来设置相关信息:

  • (1)、@property(nonatomic) NSUInteger numberOfTouchesRequired;指定该手势处理器只处理几个手指的触碰事件;
  • (2)、@property(nonatomic) UISwipeGestureRecognizerDirection direction;指定该手势处理器处理该方向的轻扫.该属性支持:
typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
    UISwipeGestureRecognizerDirectionRight = 1 << 0,
    UISwipeGestureRecognizerDirectionLeft  = 1 << 1,
    UISwipeGestureRecognizerDirectionUp    = 1 << 2,
    UISwipeGestureRecognizerDirectionDown  = 1 << 3
};

实例:

- (void)viewDidLoad {
    [super viewDidLoad];
    //设置图片直接显示在中间(不进行任何缩放)
    self.view.backgroundColor = [UIColor lightGrayColor];
    gv = [[UIImageView alloc]initWithFrame:CGRectMake(10, 200, self.view.frame.size.width - 20, 200)];
    gv.layer.borderWidth = 2;
    gv.layer.cornerRadius = 6;
    //设置gv控件支持用户交互
    gv.userInteractionEnabled = YES;
    //设置gv控件支持多点触碰
    gv.multipleTouchEnabled = YES;
    [self.view addSubview:gv];
    label = [[UILabel alloc]initWithFrame:CGRectMake(20, 50, gv.frame.size.width - 40, 80)];
    label.numberOfLines = 2;
    [gv addSubview:label];
    for (int i = 0 ; i < 4; i ++) {
        //创建手势处理器,指定使用该控制器的handleSwipe:方法处理轻扫手势
        UISwipeGestureRecognizer* gesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(handleSwipe:)];
        //设置该手势处理器只处理i个手指的轻扫手势
        gesture.numberOfTouchesRequired = 1;
        //指定该手势处理器只处理1 << i方向的轻扫手势
        gesture.direction = 1 << i;
        [gv addGestureRecognizer:gesture];
    }
}
- (void)handleSwipe:(UISwipeGestureRecognizer *)gesture{
    //获取手势的方向
    NSUInteger direction = gesture.direction;
    //根据手势方向的值得到方向字符串
    NSString* dirStr = direction == [email protected]"向右":(direction == [email protected]"向左":(direction == [email protected]"向上":@"向下"));
    NSLog(@"dirStr = %@",dirStr);
    NSUInteger touchNum = gesture.numberOfTouchesRequired;
    label.text = [NSString stringWithFormat:@"用户使用%ld个手指进行轻扫,方向为%@",touchNum,dirStr];
    //指定5秒后清除label的文本
    [label performSelector:@selector(setText:) withObject:@"" afterDelay:5];
}

5、使用UIPanGestureRecognizer处理拖动手势

UIPanGestureRecognizer定义了如下的方法来获取拖动相关信息:

  • (1)、@property (nonatomic) NSUInteger minimumNumberOfTouches;设置移动手势处理器最少需要几个手指拖动;
  • (2)、@property (nonatomic) NSUInteger maximumNumberOfTouches;设置移动手势处理器最多支持几个手指拖动;
  • (3)、- (CGPoint)translationInView:(UIView *)view;获取该拖动手势在指定控件上的位移.该方法返回一个CGPoint结构体数据,该结构体中x变量的值代表水平方向的位移,y变量的值代表了垂直方向的位移;
  • (4)、- (CGPoint)velocityInView:(UIView *)view; 获取该拖动手势在指定控件上的拖动速度.该方法返回一个CGPoint结构体数据,该结构体中x变量的值代表了水平方向的速度,y变量的值代表了垂直方向的速度.

例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    //设置图片直接显示在中间(不进行任何缩放)
    self.view.backgroundColor = [UIColor lightGrayColor];
    gv = [[UIImageView alloc]initWithFrame:CGRectMake(10, 200, self.view.frame.size.width - 20, 200)];
    gv.layer.borderWidth = 2;
    gv.layer.cornerRadius = 6;
    //设置gv控件支持用户交互
    gv.userInteractionEnabled = YES;
    //设置gv控件支持多点触碰
    gv.multipleTouchEnabled = YES;
    [self.view addSubview:gv];
    label = [[UILabel alloc]initWithFrame:CGRectMake(20, 50, gv.frame.size.width - 40, 80)];
    label.numberOfLines = 3;
    [gv addSubview:label];
    //创建手势处理器,指定使用该控制器的handlePan:方法处理手势
    UIPanGestureRecognizer* gesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePan:)];
    //设置该手势处理器最少需要1个手指
    gesture.minimumNumberOfTouches = 1;
    //设置该手势处理器最多需要2个手指
    gesture.maximumNumberOfTouches = 2;
    [gv addGestureRecognizer:gesture];
}
//实现手势处理器的方法,该方法应该生命一个形参
//当该方法被激发时,手势处理器会作为参数传给该方法的参数
- (void)handlePan:(UIPanGestureRecognizer *)gesture{
    CGPoint velocity = [gesture velocityInView:gv];
    CGPoint translation = [gesture translationInView:gv];
    label.text = [NSString stringWithFormat:@"水平速度为:%g,垂直速度为:%g,水平距离为:%g,垂直距离为:%g",velocity.x,velocity.y,translation.x,translation.y];
}

6、使用UILongGestureRecognizer处理长按手势

UILongGestureRecognizer定义了如下属性来设置该手势处理器的相关信息:

  • (1)、@property (nonatomic) CFTimeInterval minimumPressDuration;指定用户至少在屏幕上按下多少秒才会触发该长安手指,该属性默认值为0.5;
  • (2)、@property (nonatomic) NSUInteger numberOfTapsRequired;指定必须使用几个手指在屏幕上长按才会触发该手势;
  • (3)、@property (nonatomic) CGFloat allowableMovement;指定该长按手势允许用户移动手指的最大距离,如果用户手指按下时移动超过了该距离,则长按手势失效.

实例:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor lightGrayColor];
    //创建一个NSMutableArray集合 用于保存过个按钮
    bnArray = [[NSMutableArray alloc]init];
    //创建一个手势处理器 用于检测.处理长按手势
    UILongPressGestureRecognizer* gesture = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
    //为该控件添加手势处理器
    [self.view addGestureRecognizer:gesture];
}
- (void)longPress:(UILongPressGestureRecognizer *)gesture{
    //创建一个按钮
    UIButton* bn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    //获取NSArray中已经包含了几个按钮
    NSInteger count = bnArray.count;
    //计算当前添加的按钮位于第几行第几列
    NSInteger row = count/3;
    NSInteger col = count%3;
    //为按钮设置文本
    [bn setTitle:[NSString stringWithFormat:@"按钮%ld",bnIndex] forState:UIControlStateNormal];
    //设置该bn按钮的大小和位置
    bn.frame = CGRectMake(MARGINE + col * CELL_WIDTH, row * CELL_HEIGHT + MARGINE, BUTTON_WIDTH, BUTTON_HEIHGT);
    //为该按钮添加事件处理方法
    [bn addTarget:self action:@selector(remove:) forControlEvents:UIControlEventTouchUpInside];
    [bnArray addObject:bn];
    //将按钮添加到应用界面的UIView控件中
    [self.view addSubview:bn];
    bnIndex ++;
}
- (void)remove:(id)sender{
    //删除事件源控件(激发该事件的按钮)
    [sender removeFromSuperview];
    //将触发该事件的按钮从NSMutableArray集合中删除
    [bnArray removeObject:sender];
    [self rearrange];
}
- (void)rearrange{
    //重新计算每个按钮的大小和位置
    for (int i = 0; i < bnArray.count ; i ++) {
        NSInteger row = i/3;
        NSInteger col = i%3;
        UIButton* bn = [bnArray objectAtIndex:i];
        bn.frame = CGRectMake(MARGINE +  col * CELL_WIDTH, row * CELL_HEIGHT + MARGINE, BUTTON_WIDTH, BUTTON_HEIHGT);
    }
}

四、创建和使用自定义手势处理器

1、开发自定义手势处理器

开发自定义手势处理器的步骤比较简单,按如下步骤操作即可:
①、创建继承UIGestureRecognizer的子类;
②、重写UIGestureRecognizer基类的触碰相关的4个方法.通过在这些方法中识别用户手指划过的痕迹—当用户手指划过的痕迹符合手势要求时,程序将该手势state设为UIGestureRecognizerStateEnaded即可.

2、使用自定义手势处理器

待定

五、手势互斥

手势识别是具有互斥的原则的,比如单击和双击,如果它识别出一种手势,其后的手势将不被识别。所以对于关联手势,要做特殊处理以帮助程序甄别,应该把当前手势归结到哪一类手势里面。

比如,单击和双击并存时,如果不做处理,它就只能发送出单击的消息。为了能够识别出双击手势,就需要做一个特殊处理逻辑,即先判断手势是否是双击,在双击失效的情况下作为单击手势处理。使用
[A requireGestureRecognizerToFail:B]函数,它可以指定当A手势发生时,即便A已经滿足条件了,也不会立刻触发,会等到指定的手势B确定失败之后才触发。

- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;

//例如
[singleRecognizer requireGestureRecognizerToFail:doubleRecognizer]; 

六、UIGestureRecognizerDelegate

1、

// called when a gesture recognizer attempts to transition out of UIGestureRecognizerStatePossible. returning NO causes it to transition to UIGestureRecognizerStateFailed
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;

手势可能发生的条件,比如某些特殊情况下,不想让此手势发生,就return NO。
2、

// called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other
// return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)
//
// note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture‘s delegate may return YES
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

gestureRecognizer可能被otherGestureRecognizer手势识别引起阻塞,返回YES为允许同时识别。默认返回NO(默认情况下同一种手势只能识别一个)
3、

// called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;

方法touchesBegan:withEvent:执行前将调用该方法,返回YES执行手势事件;返回NO,将不响应手势事件。

以上是关于:多点触摸与手势检测的主要内容,如果未能解决你的问题,请参考以下文章

Android点击事件之多点触摸与手势识别

WPF 多点触摸开发[2]:WPF触摸的几个手势的执行顺序

微信小程序~触摸相关事件(拖拽操作手势识别多点触控)

iOS 数据结构维护多点触控痕迹?

读书笔记iOS-多点触摸事件与界面几何

通过Swift中的触摸检测应用程序何时处于活动状态或非活动状态[重复]