第一章:多点触摸与手势检测

来源:互联网 发布:网络流行语的利与弊 编辑:程序博客网 时间:2024/05/06 05:51

一、响应者链

只要继承了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 == UISwipeGestureRecognizerDirectionRight?@"向右":(direction == UISwipeGestureRecognizerDirectionLeft?@"向左":(direction == UISwipeGestureRecognizerDirectionUp?@"向上":@"向下"));    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,将不响应手势事件。

0 0
原创粉丝点击