55、iOS 触摸与手势识别开发指南

iOS 触摸与手势识别开发指南

一、触摸操作与模拟器使用

在开发过程中,我们可以利用模拟器进行一些触摸操作的模拟。在模拟器里,按住 option 键会出现一对代表两根手指的点,再按住 shift 键可锁定这两点的相对位置,此时就能进行双指滑动等双指手势操作。不过,模拟器无法进行三指及以上的手势操作,但通过 option 键和 shift 键的组合,能完成大部分双指手势。

若能在 iPhone 或 iPod touch 上运行程序,可尝试同时注册多个触摸点。比如,依次用单指、双指、三指进行拖动操作,还可以进行双击和三击屏幕,看看能否通过双指点击使点击计数增加。通过操作 TouchExplorer 应用程序,熟悉触摸方法的工作原理,之后就可以开始学习检测常见的滑动手势了。

二、滑动手势检测应用的构建

2.1 应用概述

我们要构建一个仅用于检测水平和垂直滑动手势的应用。当手指在屏幕上从左到右、从右到左、从上到下或从下到上滑动时,应用会在屏幕顶部显示一条消息,提示检测到了滑动,该消息会显示几秒。

2.2 项目创建与代码添加

  1. 创建项目 :在 Xcode 中使用 Single View Application 模板创建一个新的 iPhone 项目,命名为 Swipes
  2. 修改 BIDViewController.h 文件
#import <UIKit/UIKit.h>

@interface BIDViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *label;
@property CGPoint gestureStartPoint;
@end

此代码声明了一个标签的输出口和一个用于存储用户首次触摸位置的变量。
3. 编辑 BIDViewController.xib 文件
- 确保视图的 User Interaction Enabled Multiple Touch 选项都被勾选。
- 从库中拖动一个 Label 到视图窗口,设置该标签使其宽度与视图的蓝色参考线一致,且文本居中显示。可根据需要调整文本属性,方便阅读。
- 从 File’s Owner 图标控制拖动到标签,将其连接到 label 输出口。
- 双击标签并删除其文本。
4. 修改 BIDViewController.m 文件

#import "BIDViewController.h"

#define kMinimumGestureLength    25
#define kMaximumVariance         5

@implementation BIDViewController
@synthesize label;
@synthesize gestureStartPoint;

- (void)eraseText {
    label.text = @"";
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    self.label = nil;
}

#pragma mark -
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    gestureStartPoint = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint currentPosition = [touch locationInView:self.view];

    CGFloat deltaX = fabsf(gestureStartPoint.x - currentPosition.x);
    CGFloat deltaY = fabsf(gestureStartPoint.y - currentPosition.y);

    if (deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance) {
        label.text = @"Horizontal swipe detected";
        [self performSelector:@selector(eraseText)
                   withObject:nil afterDelay:2];
    } else if (deltaY >= kMinimumGestureLength &&
               deltaX <= kMaximumVariance){
        label.text = @"Vertical swipe detected";
        [self performSelector:@selector(eraseText) withObject:nil
                   afterDelay:2];
    }
}

@end

这里定义了最小滑动长度为 25 像素,最大偏差为 5 像素。在 touchesBegan:withEvent: 方法中,获取用户首次触摸的位置;在 touchesMoved:withEvent: 方法中,计算手指水平和垂直移动的距离,判断是否满足滑动条件,若满足则显示相应的滑动检测消息,并在 2 秒后清除该消息。

2.3 编译与运行

完成上述代码添加后,编译并运行应用。若点击拖动无结果,需耐心尝试垂直或水平的直线拖动,直至掌握滑动操作。

2.4 滑动检测流程

graph TD;
    A[用户触摸屏幕] --> B[记录首次触摸位置];
    B --> C[手指移动];
    C --> D[计算水平和垂直移动距离];
    D --> E{是否满足滑动条件};
    E -- 是 --> F[显示滑动检测消息];
    E -- 否 --> C;
    F --> G[2秒后清除消息];

三、自动手势识别

3.1 手动检测滑动的问题

前面手动检测滑动的方法虽然可行,但代码复杂度主要集中在 touchesMoved:withEvent: 方法中。不过,iOS 提供了 UIGestureRecognizer 类,可简化手势检测过程,无需手动监控手指移动的所有事件。

3.2 修改 Swipes 应用以使用手势识别器

  1. 删除原有方法 :打开 BIDViewController.m 文件,删除 touchesBegan:withEvent: touchesMoved:withEvent: 方法。
  2. 添加新方法
- (void)reportHorizontalSwipe:(UIGestureRecognizer *)recognizer {
    label.text = @"Horizontal swipe detected";
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

- (void)reportVerticalSwipe:(UIGestureRecognizer *)recognizer {
    label.text = @"Vertical swipe detected";
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

这两个方法用于处理滑动手势触发的操作。
3. 修改 viewDidLoad 方法

- (void)viewDidLoad
{
    [super viewDidLoad];

    UISwipeGestureRecognizer *vertical = [[UISwipeGestureRecognizer alloc]  
        initWithTarget:self action:@selector(reportVerticalSwipe:)];
    vertical.direction = UISwipeGestureRecognizerDirectionUp|  
        UISwipeGestureRecognizerDirectionDown;
    [self.view addGestureRecognizer:vertical];

    UISwipeGestureRecognizer *horizontal = [[UISwipeGestureRecognizer alloc]  
        initWithTarget:self action:@selector(reportHorizontalSwipe:)];
    horizontal.direction = UISwipeGestureRecognizerDirectionLeft|  
        UISwipeGestureRecognizerDirectionRight;
    [self.view addGestureRecognizer:horizontal];
}

此代码创建并配置了垂直和水平滑动手势识别器,并将它们添加到视图中。当用户进行相应的滑动操作时,会触发对应的方法。

3.3 两种方法对比

方法类型 代码复杂度 实现难度
手动检测 较高,主要在 touchesMoved:withEvent: 方法 较难,需手动计算手指移动距离
手势识别器 较低 简单,只需创建和配置识别器

使用手势识别器的代码更易于理解和编写,无需考虑手指移动的计算问题。

四、多手指滑动手势的实现

4.1 单指滑动回顾

在之前的 Swipes 应用中,仅考虑了单指滑动,直接从触摸集合中获取任意对象来确定手指位置。这种方法适用于只关注单指滑动的情况。

4.2 多手指滑动的实现

  1. 复制项目 :复制 Swipes 项目文件夹。
  2. 修改 viewDidLoad 方法
- (void)viewDidLoad
{
    [super viewDidLoad];

    for (NSUInteger touchCount = 1; touchCount <= 5; touchCount++) {
        UISwipeGestureRecognizer *vertical;
        vertical = [[UISwipeGestureRecognizer alloc] initWithTarget:self
            action:@selector(reportVerticalSwipe:)];
        vertical.direction = UISwipeGestureRecognizerDirectionUp
            | UISwipeGestureRecognizerDirectionDown;
        vertical.numberOfTouchesRequired = touchCount;
        [self.view addGestureRecognizer:vertical];

        UISwipeGestureRecognizer *horizontal;
        horizontal = [[UISwipeGestureRecognizer alloc] initWithTarget:self
            action:@selector(reportHorizontalSwipe:)];
        horizontal.direction = UISwipeGestureRecognizerDirectionLeft  
            | UISwipeGestureRecognizerDirectionRight;
        horizontal.numberOfTouchesRequired = touchCount;
        [self.view addGestureRecognizer:horizontal];
    }
}

通过循环创建多个不同触摸点数的垂直和水平滑动手势识别器。
3. 添加描述方法

- (NSString *)descriptionForTouchCount:(NSUInteger)touchCount {
    switch (touchCount) {
        case 2:
            return @"Double ";
        case 3:
            return @"Triple ";
        case 4:
            return @"Quadruple ";
        case 5:
            return @"Quintuple ";
        default:
            return @"";
    }
}

该方法用于根据触摸点数返回相应的描述。
4. 修改报告方法

- (void)reportHorizontalSwipe:(UIGestureRecognizer *)recognizer {
    label.text = [NSString stringWithFormat:@"%@Horizontal swipe detected",
        [self descriptionForTouchCount:[recognizer numberOfTouches]]];
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

- (void)reportVerticalSwipe:(UIGestureRecognizer *)recognizer {
    label.text = [NSString stringWithFormat:@"%@Vertical swipe detected",
        [self descriptionForTouchCount:[recognizer numberOfTouches]]];
    [self performSelector:@selector(eraseText) withObject:nil afterDelay:2];
}

修改后的报告方法会根据触摸点数显示相应的滑动检测消息。

4.3 注意事项

在进行多手指滑动时,要注意手指之间不能靠得太近,否则可能会被误识别为单指触摸。因此,不建议将四指或五指滑动用于重要的手势操作,因为很多人的手指可能无法有效完成这些操作。

五、多点击检测

5.1 多点击检测的问题

TouchExplorer 应用中,虽然能检测到多次点击,但当需要根据点击次数执行不同操作时,会出现问题。例如,用户进行三击操作时,会分别收到单击、双击和三击的通知,若要对双击和三击执行不同操作,这些重复通知可能会导致混乱。

5.2 解决方案

苹果提供了一种机制,让多个手势识别器能协同工作。通过给一个手势识别器设置约束,使其在另一个手势识别器未触发时才触发自己的关联方法。

5.3 示例代码

UITapGestureRecognizer 为例,假设要为视图定义单点击和双点击的不同操作:

UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:
    self action:@selector(doSingleTap)];
singleTap.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:singleTap];

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:
    self action:@selector(doDoubleTap)];
doubleTap.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTap];

[singleTap requireGestureRecognizerToFail:doubleTap];

上述代码中,给 singleTap 设置了约束,使其在 doubleTap 未触发时才执行自己的方法。

5.4 构建 TapTaps 应用

  1. 创建项目 :在 Xcode 中使用 Single View Application 模板创建一个新的 iPhone 项目,命名为 TapTaps
  2. 修改 BIDViewController.h 文件
#import <UIKit/UIKit.h>

@interface BIDViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *singleLabel;
@property (weak, nonatomic) IBOutlet UILabel *doubleLabel;
@property (weak, nonatomic) IBOutlet UILabel *tripleLabel;
@property (weak, nonatomic) IBOutlet UILabel *quadrupleLabel;
- (void)tap1;
- (void)tap2;
- (void)tap3;
- (void)tap4;
- (void)eraseMe:(UILabel *)label;
@end

此代码声明了四个标签的输出口和处理不同点击次数的方法。
3. 编辑 BIDViewController.xib 文件
- 从库中添加四个 Label 到视图中,设置它们的宽度与视图的蓝色参考线一致,文本居中显示。可根据需要设置不同颜色。
- 从 File’s Owner 图标控制拖动到每个标签,分别连接到 singleLabel doubleLabel tripleLabel quadrupleLabel 输出口。
- 双击每个标签并删除其文本。
4. 修改 BIDViewController.m 文件

#import "BIDViewController.h"

@implementation BIDViewController
@synthesize singleLabel;
@synthesize doubleLabel;
@synthesize tripleLabel;
@synthesize quadrupleLabel;

- (void)tap1 {
    singleLabel.text = @"Single Tap Detected";
    [self performSelector:@selector(eraseMe:)
        withObject:singleLabel afterDelay:1.6f];
}

- (void)tap2 {
    doubleLabel.text = @"Double Tap Detected";
    [self performSelector:@selector(eraseMe:)
        withObject:doubleLabel afterDelay:1.6f];
}

- (void)tap3 {
    tripleLabel.text = @"Triple Tap Detected";
    [self performSelector:@selector(eraseMe:)
        withObject:tripleLabel afterDelay:1.6f];
}

- (void)tap4 {
    quadrupleLabel.text = @"Quadruple Tap Detected";
    [self performSelector:@selector(eraseMe:)
        withObject:quadrupleLabel afterDelay:1.6f];
}

- (void)eraseMe:(UILabel *)label {
    label.text = @"";
}

- (void)viewDidUnload {
    [super viewDidUnload];
    self.singleLabel = nil;
    self.doubleLabel = nil;
    self.tripleLabel = nil;
    self.quadrupleLabel = nil;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    UITapGestureRecognizer *singleTap =
        [[UITapGestureRecognizer alloc] initWithTarget:self
                                                action:@selector(tap1)];
    singleTap.numberOfTapsRequired = 1;
    singleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:singleTap];

    UITapGestureRecognizer *doubleTap =
        [[UITapGestureRecognizer alloc] initWithTarget:self
                                                action:@selector(tap2)];
    doubleTap.numberOfTapsRequired = 2;
    doubleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:doubleTap];
    [singleTap requireGestureRecognizerToFail:doubleTap];

    UITapGestureRecognizer *tripleTap =
        [[UITapGestureRecognizer alloc] initWithTarget:self
                                                action:@selector(tap3)];
    tripleTap.numberOfTapsRequired = 3;
    tripleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:tripleTap];
    [doubleTap requireGestureRecognizerToFail:tripleTap];

    UITapGestureRecognizer *quadrupleTap =
        [[UITapGestureRecognizer alloc] initWithTarget:self
                                                action:@selector(tap4)];
    quadrupleTap.numberOfTapsRequired = 4;
    quadrupleTap.numberOfTouchesRequired = 1;
    [self.view addGestureRecognizer:quadrupleTap];
    [tripleTap requireGestureRecognizerToFail:quadrupleTap];
}

viewDidLoad 方法中,依次创建了单点击、双点击、三点击和四点击的手势识别器,并为它们设置了触发条件和关联方法。

5.5 点击检测流程

graph TD;
    A[用户点击屏幕] --> B{是否为单点击};
    B -- 是 --> C{等待是否有第二次点击};
    C -- 否 --> D[触发单点击方法];
    C -- 是 --> E{是否为双点击};
    E -- 是 --> F{等待是否有第三次点击};
    F -- 否 --> G[触发双点击方法];
    F -- 是 --> H{是否为三点击};
    H -- 是 --> I{等待是否有第四次点击};
    I -- 否 --> J[触发三点击方法];
    I -- 是 --> K[触发四点击方法];

通过以上步骤,我们可以在 iOS 应用中实现滑动和点击等常见手势的检测,并且能处理多手指和多点击的复杂情况。

六、捏合与旋转手势识别

6.1 捏合手势

捏合手势常用于缩放操作,在 iOS 中可使用 UIPinchGestureRecognizer 来实现。以下是一个简单的示例代码:

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
    [self.imageView addGestureRecognizer:pinchRecognizer];
}

- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer {
    recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
    recognizer.scale = 1;
}

@end

上述代码中:
1. 在 viewDidLoad 方法里创建了一个 UIPinchGestureRecognizer 对象,并将其添加到 UIImageView 上。
2. handlePinch: 方法用于处理捏合手势,通过 CGAffineTransformScale 函数对视图进行缩放,同时将 recognizer.scale 重置为 1。

6.2 旋转手势

旋转手势可使用 UIRotationGestureRecognizer 来实现,示例代码如下:

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIRotationGestureRecognizer *rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
    [self.imageView addGestureRecognizer:rotationRecognizer];
}

- (void)handleRotation:(UIRotationGestureRecognizer *)recognizer {
    recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);
    recognizer.rotation = 0;
}

@end

代码说明:
1. 在 viewDidLoad 方法中创建 UIRotationGestureRecognizer 对象并添加到 UIImageView 上。
2. handleRotation: 方法使用 CGAffineTransformRotate 函数对视图进行旋转,同时将 recognizer.rotation 重置为 0。

6.3 捏合与旋转手势识别流程

graph TD;
    A[用户进行捏合或旋转操作] --> B[手势识别器检测到操作];
    B --> C{是捏合手势吗};
    C -- 是 --> D[调用捏合处理方法进行缩放];
    C -- 否 --> E[调用旋转处理方法进行旋转];

七、长按手势识别

7.1 长按手势的实现

长按手势可通过 UILongPressGestureRecognizer 来实现,以下是示例代码:

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *longPressView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
    longPressRecognizer.minimumPressDuration = 1.5; // 设置长按时间为 1.5 秒
    [self.longPressView addGestureRecognizer:longPressRecognizer];
}

- (void)handleLongPress:(UILongPressGestureRecognizer *)recognizer {
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        NSLog(@"长按开始");
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
        NSLog(@"长按结束");
    }
}

@end

代码解释:
1. 在 viewDidLoad 方法中创建 UILongPressGestureRecognizer 对象,设置 minimumPressDuration 为 1.5 秒,并将其添加到 longPressView 上。
2. handleLongPress: 方法根据手势状态输出相应的日志信息。

7.2 不同手势识别器对比

手势识别器 用途 关键属性
UISwipeGestureRecognizer 检测滑动手势 direction numberOfTouchesRequired
UITapGestureRecognizer 检测点击手势 numberOfTapsRequired numberOfTouchesRequired
UIPinchGestureRecognizer 检测捏合手势 scale
UIRotationGestureRecognizer 检测旋转手势 rotation
UILongPressGestureRecognizer 检测长按手势 minimumPressDuration

八、手势冲突处理

8.1 冲突场景

在实际开发中,可能会出现多种手势识别器同时存在,导致手势冲突的情况。例如,同时添加了点击和长按手势识别器,当用户长按屏幕时,可能会先触发点击手势。

8.2 解决方法

可使用 requireGestureRecognizerToFail: 方法来解决冲突。以下是示例代码:

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *gestureView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];

    [self.gestureView addGestureRecognizer:tapRecognizer];
    [self.gestureView addGestureRecognizer:longPressRecognizer];

    [tapRecognizer requireGestureRecognizerToFail:longPressRecognizer];
}

- (void)handleTap:(UITapGestureRecognizer *)recognizer {
    NSLog(@"点击手势触发");
}

- (void)handleLongPress:(UILongPressGestureRecognizer *)recognizer {
    NSLog(@"长按手势触发");
}

@end

代码说明:
通过 [tapRecognizer requireGestureRecognizerToFail:longPressRecognizer]; 让点击手势识别器在长按手势识别器未触发时才触发自己的方法,从而避免冲突。

8.3 手势冲突处理流程

graph TD;
    A[用户进行手势操作] --> B{是否满足长按条件};
    B -- 是 --> C[触发长按手势方法];
    B -- 否 --> D{是否满足点击条件};
    D -- 是 --> E[触发点击手势方法];
    D -- 否 --> F[无手势触发];

九、总结

在 iOS 开发中,手势识别是非常重要的一部分,它能为用户提供更加友好和便捷的交互体验。本文详细介绍了滑动、点击、捏合、旋转、长按等常见手势的识别方法,以及多手指和多点击的处理方式,同时还讲解了手势冲突的解决办法。通过合理运用这些手势识别技术,开发者可以打造出功能丰富、交互性强的 iOS 应用。

以下是一个简单的开发步骤总结列表:
1. 确定应用所需的手势类型。
2. 根据手势类型选择合适的手势识别器。
3. 配置手势识别器的属性,如点击次数、触摸点数等。
4. 实现手势处理方法。
5. 处理可能出现的手势冲突。

通过以上步骤,开发者可以在 iOS 应用中轻松实现各种手势识别功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值