手势识别器内容

手势识别器(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?
相关推荐
专业开发者11 小时前
调试 iOS 蓝牙应用的新方法
物联网·macos·ios·cocoa
tangbin58308516 小时前
iOS Swift 可选值(Optional)详解
前端·ios
卷心菜加农炮1 天前
基于Python的FastAPI后端开发框架如何使用PyInstaller 进行打包与部署
ios
北极象2 天前
千问大模型接入示例
ios·iphone·qwen
ipad协议开发2 天前
企业微信 iPad 协议应用机器人开发
ios·企业微信·ipad
QuantumLeap丶2 天前
《Flutter全栈开发实战指南:从零到高级》- 26 -持续集成与部署
android·flutter·ios
2501_915918412 天前
TCP 抓包分析在复杂网络问题中的作用,从连接和数据流层面理解系统异常行为
网络·网络协议·tcp/ip·ios·小程序·uni-app·iphone
二流小码农3 天前
鸿蒙开发:个人开发者如何使用华为账号登录
android·ios·harmonyos
wvy3 天前
Xcode 26还没有适配SceneDelegate的app建议尽早适配
ios
游戏开发爱好者83 天前
苹果 App 上架流程,结合 Xcode、CI 等常见工具
macos·ios·ci/cd·小程序·uni-app·iphone·xcode