手势识别器内容

手势识别器(Gesture Recognizer)用于识别触摸序列并触发响应事件。当手势识别器识别到一个手势或手势发生变化时,会触发响应事件。UIGestureRecognizer类是具体手势识别器的基础类 ,不能直接使用。只能使用UIGestureRecognizer的子类识别手势,每一个子类识别一个特定的手势。UIGestureRecognizer有以下几个子类:

  1. UITapGestureRecognizer:点击手势识别器,手势可以是一次点击或多次点击,可以是一个手指也可以是多个手指。点击手势是最为常用的手势。
  2. UISwipeGestureRecognizer:滑动手势识别器,滑动方向可以是上下左右任一方向。滑动手势的典型示例是查看照片的应用程序,我们使用手指从一张照片滑动到另一张照片。
  3. UIPanGestureRecognizer:平移手势识别器,也称为拖动手势识别器。当用户平移视图时,必须保持一个或多个手指始终按压在视图上。
  4. UIPinchGestureRecognizer:捏合手势识别器,需要两个手指同时触摸视图。当两个手指靠近时,视图缩小;当两根手指远离时,视图放大。
  5. UIRotationGestureRecognizer:旋转手势识别器,需要两个手指同时触摸视图。当用户手指彼此相对做圆周运动时,对应视图会以相同的方向和速度旋转。
  6. UILongPressGestureRecognizer:长按手势识别器,想要成功触发长按手势,需使用一个或多个手指在视图上按压不小于minimumPressDuration设定的时长,默认0.5秒,并且长按时手指移动的距离要小于allowableMovement设定的距离,默认距离10points。
  7. UIScreenEdgePanGestureRecognizer:屏幕边缘轻扫手势识别器,继承自UIPanGestureRecognizer,前面六个手势识别器均继承自UIGestureRecognizer。屏幕边缘轻扫手势识别器只能识别在屏幕边缘附近开始的手势。与滑动手势很像,不同之处在于前者必须从屏幕边缘开始。可以使用屏幕边缘手势来启动视图控制器转换。

整体而言,手势分为两种,离散或者连续

离散的手势识别器从手势开始到结束只向目标发送单个动作消息,例如点击手势、滑动手势。连续手势的手势识别器为每个增量变化发送动作消息,直到多点触摸序列结束,例如平移手势、捏合手势、旋转手势

一般来说,手势识别器调用响应方法时,会把手势识别器自己作为参数,以便在处理事件时使用额外信息

swift 复制代码
- (void)handleTapGesture;
- (void)handleTapGestureWithGestureRecognizer:(UITapGestureRecognizer *)gestureRecognizer;

当然这样的写法并不是必须的,但是如果你需要获取手势在视图控制器中的位置,可以通过参数gestureRecognizer 来调用locationInView 方法来获取

一个手势识别器只能添加到一个视图,一个视图上可以添加多个手势识别器

for example:

一个照片视图可添加捏合和旋转手势,这样就可以实现放大缩小或者旋转图片

但是手势识别器一次只能识别一个手势,它不能把你的手势同时识别为捏合和旋转手势

实践

本次使用简单的tabbar来分成不同的页面来进行讲解,并且只涉及到前六个手势识别器

点击手势识别器

objectivec 复制代码
// 创建点击手势识别器
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
tapGesture.numberOfTapsRequired = 1;
[self.targetView addGestureRecognizer:tapGesture]; 

可以看出来其实基础的手势识别器是很简单的

我们只需要创建,指定目标,指定响应方法,加入到你想指定的视图里即可

numberOfTapsRequired 指定了点击手势识别器触发时需要的点击次数

也就是如果想实现双击,在这里将次数改为2即可

此外手势识别器还有一个属性:

numberOfTouchesRequired 控制了手势需要的指头数量,即需要几根指头按下点击

可能在某些特定的地方可以用到

滑动手势识别器

滑动手势识别器叫做

UILongPressGestureRecognizer ,它的创建和使用基本上是相似的

UILongPressGestureRecognizer类包含以下四个属性:

  • minimumPressDuration:触发长按手势所需按压的最短时间,单位是秒,默认为0.5秒
  • numberOfTouchesRequired:触发长按手势所需手指数,默认为1
  • numberOfTapsRequired:触发长按手势所需点击数,默认为0
  • allowableMovement:手指按压住视图后允许手指移动的最大距离,单位point,默认10 points

但在长按中,很明显是有一个过程的,即按下出触发手势识别器和抬起两个过程

为了识别当前属于什么阶段,UILongPressGestureRecognizer还有.state来帮助你判断当前状态

我们结合一下UIImpactFeedbackGenerator就可以轻松做到当在识别时触发震动这个反馈

这里给出响应方法的代码,创建和配置代码就不展示了

objectivec 复制代码
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateBegan) {
        // 长按开始
        self.infoLabel.text = @"长按中...";
        self.targetView.backgroundColor = [UIColor systemRedColor];
        
        // 添加震动反馈
        UIImpactFeedbackGenerator *feedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
        [feedback impactOccurred];
        
        // 旋转动画
        [UIView animateWithDuration:0.3 animations:^{
            self.targetView.transform = CGAffineTransformMakeRotation(M_PI);
        }];
    } else if (gesture.state == UIGestureRecognizerStateEnded) {
        // 长按结束
        self.infoLabel.text = @"长按完成";
        self.targetView.backgroundColor = [UIColor systemGreenColor];
        
        // 恢复旋转
        [UIView animateWithDuration:0.3 animations:^{
            self.targetView.transform = CGAffineTransformIdentity;
        }];
    }
}

滑动手势识别器

滑动手势可以滑向上下左右的任一方向,但不包括对角线方向

使用UISwipeGestureRecognizerDirection类提供的dircetion属性(枚举值)指定手势的滑动方向

如果没有设置滑动方向,默认向右侧滑动

每一个手势识别器只能识别一个滑动方向,所以如果需要识别多个方向,需要添加多个手势识别器

另外,滑动手势识别器的动作方法在滑动手势结束那一刻被调用

这里直接给出配置方法

objectivec 复制代码
// 创建各个方向的滑动手势识别器
    UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
    swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
    [self.targetView addGestureRecognizer:swipeUp];
    
    UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
    swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
    [self.targetView addGestureRecognizer:swipeDown];
    
    UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
    swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    [self.targetView addGestureRecognizer:swipeLeft];
    
    UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
    swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
    [self.targetView addGestureRecognizer:swipeRight];

对应的,由于我们这里加入了四个手势识别器,我们也得在触发方法里去识别这四个手势识别器,并给出相应的反馈,这里我还同时创建了一个变化矩阵来执行对应的动画

objectivec 复制代码
switch (gesture.direction) {
        case UISwipeGestureRecognizerDirectionUp:
            direction = @"向上";
            translation = CGPointMake(0, -50);
            break;
        case UISwipeGestureRecognizerDirectionDown:
            direction = @"向下";
            translation = CGPointMake(0, 50);
            break;
        case UISwipeGestureRecognizerDirectionLeft:
            direction = @"向左";
            translation = CGPointMake(-50, 0);
            break;
        case UISwipeGestureRecognizerDirectionRight:
            direction = @"向右";
            translation = CGPointMake(50, 0);
            break;
        default:
            break;
    }

这样,这个页面就可以被四个不同方向的滑动所影响

平移手势识别器

平移手势识别器(UIPanGestureRecognizer),或称为拖动手势识别器

这个手势可以使用户按压住视图在窗口内随意拖动

在用户拖动的过程中,必须保持手指一直按压在视图上。使用UIPanGestureRecognizer类的velocityInView: 方法可以获取拖动手势移动的速度

objectivec 复制代码
// 创建拖动手势识别器
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    [self.targetView addGestureRecognizer:panGesture];
...

// 响应方法
- (void)handlePan:(UIPanGestureRecognizer *)gesture {
  CGPoint touchPoint = [gesture locationInView:self.targetView];
  self.targetView.center = touchPoint;
}

旋转和捏合手势

旋转和捏合手势我放在了一块来讲解

捏合手势识别器(UIPinchGestureRecognizer)当用户两个手指靠近时缩小视图,两个手指远离时放大视图

最常见的就是浏览照片时对照片捏合缩放,但在这一部分没有使用UIImageView,而是在UIView上添加UIPinchGestureRecognizer

捏合手势识别器必须两个手指同时操作,这一点与前面几个示例不同

旋转手势识别器(UIRotationGestureRecognizer)与捏合手势类似,都需要两个手指,通过修改rotation来改变视图的变换。通常,旋转手势与其他手势组合使用

objectivec 复制代码
		// 创建捏合手势识别器
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
    [self.targetView addGestureRecognizer:pinchGesture];
    
    ...
    // 创建旋转手势识别器
    UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
    [self.targetView addGestureRecognizer:rotationGesture]; 

先来讲解捏合操作

捏合操作可能看起来非常简单,但在实际处理中,我们可能会遇到相当复杂的问题,例如视图不断疯狂放大(没有将属性重置),放大效果不跟手等

想要优秀的处理捏合操作的反馈部分,需要属性Core Animation框架内容中的返射矩阵变换的内容,可以参考我之前的文章:

Core Animation基础

先给出我的响应方法:

objectivec 复制代码
UIPinchGestureRecognizer 内部会计算比例:D2/D1=1.1(假设放大了 10%)- (void)handlePinch:(UIPinchGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateBegan) {
        // 捏合开始
        self.infoLabel.text = @"捏合中...";
    } else if (gesture.state == UIGestureRecognizerStateChanged) {
        // 捏合中
        self.currentScale *= gesture.scale;
        
        // 限制缩放范围
        self.currentScale = MAX(0.5, MIN(3.0, self.currentScale));
        
        self.targetView.transform = CGAffineTransformMakeScale(self.currentScale, self.currentScale);
        
        // 更新标签显示缩放比例
        self.infoLabel.text = [NSString stringWithFormat:@"缩放比例: %.2fx", self.currentScale];
        
        gesture.scale = 1.0;
    } else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) {
        // 捏合结束
        self.infoLabel.text = @"捏合完成";
    }
}

重点来看当state处于UIGestureRecognizerStateChanged 的时候

我们先来假定一个场景,我的视图为100*100规模,我们的两个指头做放大的动作

当你的两根手指放上去的时候,距离是D1,然后张开一点,距离变为了D2

此时,UIPinchGestureRecognizer 内部会计算比例:D2/D1=1.1(假设放大了 10%),pinchGestureRecognizer.scale 的值变成了 1.1,系统触发方法

再来看我们的方法,在方法内,我们先存储了scale 的数值,便于我们来进行范围限制(即最大不超过三倍,最小值不小于一半

然后我们创建了一个变化矩阵,变化矩阵会将控件的长和宽缩放到我们指定的倍率,即1.1倍

最后我们将手势识别器的scale 重新置为1

注意!!!!!

注意我对缩放倍率的记录,

objectivec 复制代码
self.currentScale *= gesture.scale;

为了限制缩放倍率,同时不影响视图,我把scale拿出来记录过,并且为自乘操作

你知道吗,如果不设置为1,或者不记录,你的控件缩放会极度炸裂

我们来看一下假如不使用本地记录当前缩放倍率会怎么样(这里一定要去看transfrom的属性特性)

在我们第一次设置缩放矩阵之后,视图假如说被设定为1.1倍

之后我们把手抬起,重新缩放

此时手势识别器的scale 会被置为1,然后重新缩放(比如1.5)

你的视图的变化矩阵会从1.1变化为1.5倍,即1.0*1.5(因为之前的变化矩阵会被覆盖)

但是我们在缩放视图的时候如果连续两次缩放,我们更加希望视图尺寸可以变为1.1*1.5

这就是为什么我们要本地记录当前的缩放倍率

最后我们来讲讲为什么需要将手势识别器置1

手势识别器,在内部识别数据变化后,会再次出发方法,所以如果布置为1,在浅浅暂停过后的放大时,你的倍率会每秒都乘那个数字

接下来讲一下旋转手势视图控制器

在旋转手势视图控制器的响应中,其实处理方法是差不多的,所以这里不在赘述,直接给出方法和效果

objectivec 复制代码
- (void)handleRotation:(UIRotationGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateBegan) {
        // 旋转开始
        self.infoLabel.text = @"旋转中...";
    } else if (gesture.state == UIGestureRecognizerStateChanged) {
        // 旋转中
        self.currentRotation += gesture.rotation;
        self.targetView.transform = CGAffineTransformMakeRotation(self.currentRotation);
        
        // 计算角度(转换为度数)
        CGFloat angleInDegrees = self.currentRotation * 180.0 / M_PI;
        self.infoLabel.text = [NSString stringWithFormat:@"旋转角度: %.1f°", angleInDegrees];
        
        gesture.rotation = 0;
    } else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) {
        // 旋转结束
        self.infoLabel.text = @"旋转完成";
    }
}

需要注意这两个手势识别器都需要本地存储当前变化,再在当前变化中叠加变化,构造变化矩阵

重点就是如何处理本地的状态记录以及手势识别器的状态叠加

参考资料:

  1. Using Gesture Recognizers to Handle Pinch, Rotate, Pan, Swipe and Tap Gestures
  2. UIGestureRecognizer
  3. Can you attach a UIGestureRecognizer to multiple views?
相关推荐
他们都不看好你,偏偏你最不争气2 小时前
【iOS】SDWebImage解析
macos·ios·objective-c·cocoa·sdwebimage
Digitally2 小时前
如何从iPhone切换到Android
android·ios·iphone
2501_916007472 小时前
苹果应用商店上架的系统逻辑,从产品开发到使用 开心上架 上架IPA 交付审核流程
android·ios·小程序·https·uni-app·iphone·webview
微声G2 小时前
Kotlin Multiplatform超详细介绍,一篇就够了
android·ios·kotlin
QuantumLeap丶3 小时前
《Flutter全栈开发实战指南:从零到高级》- 20 -主题与国际化
flutter·ios·前端框架
曾经我也有梦想3 小时前
Textture 生命周期
ios
2501_916008893 小时前
Python抓包HTTPS详解:Wireshark、Fiddler、Charles等工具使用教程
python·ios·小程序·https·uni-app·wireshark·iphone
Sheffi663 小时前
iOS Block 底层结构与变量捕获原理深度解析
ios
2501_916008894 小时前
uni-app 上架到 App Store 的项目流程,构建、打包与使用开心上架(Appuploader)上传
android·ios·小程序·https·uni-app·iphone·webview