首页 iOS开发:触摸事件和手势识别
文章
取消

iOS开发:触摸事件和手势识别

触摸事件

首先,我们来了解下什么是触摸事件。在iOS中,当用户点击、滑动、捏合等进行操作时,系统会将其转化为触摸事件并发送到应用程序中,应用程序再针对这些事件做出相应的处理。

触摸事件的过程

触摸事件在iOS中可以分为以下几种:

  • touchesBegan:withEvent::手指开始触摸屏幕时调用的方法。
  • touchesMoved:withEvent::手指在屏幕上移动时调用的方法。
  • touchesEnded:withEvent::手指离开屏幕时调用的方法。
  • touchesCancelled:withEvent::由于某种原因(比如来电)导致触摸事件取消时调用的方法。

响应触摸事件

要响应触摸事件,我们需要在视图中重写以下方法:

1
2
3
4
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

这些方法都传入了一个NSSet类型的参数touches,该集合包含了所有与此次触摸事件相关的UITouch对象。我们可以通过UITouch对象获取触摸点的坐标、时间等信息。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    NSLog(@"Touch began at point %@", NSStringFromCGPoint(touchPoint));
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    NSLog(@"Touch moved to point %@", NSStringFromCGPoint(touchPoint));
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    NSLog(@"Touch ended at point %@", NSStringFromCGPoint(touchPoint));
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Touch cancelled");
}

以下是一个简单的自定义手势识别器示例,它可以检测用户进行的快速滑动操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@interface FastSwipeGestureRecognizer : UIGestureRecognizer

@end

@implementation FastSwipeGestureRecognizer {
    CGPoint _startPoint;
    NSTimeInterval _startTime;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    _startPoint = [touch locationInView:self.view];
    _startTime = touch.timestamp;
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 如果手指移动的距离超过了设定的阈值,则认为是快速滑动
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self.view];
    CGFloat distance = fabs(currentPoint.x - _startPoint.x);
    NSTimeInterval duration = touch.timestamp - _startTime;
    if (distance > 100 && duration < 0.3) {
        self.state = UIGestureRecognizerStateRecognized;
    }
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.state = UIGestureRecognizerStateFailed;
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.state = UIGestureRecognizerStateFailed;
}

@end

多点触摸

除了单点触摸外,iOS还支持多点触摸。多点触摸指的是同时有两个或以上的触摸点。对于多点触摸,我们需要在UIView子类中重写以下方法:

1
2
3
4
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

这些方法中,touches参数中包含了所有的UITouch对象,我们可以遍历这些对象来获取每个触摸点的位置信息。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    for (UITouch *touch in touches) {
        CGPoint touchPoint = [touch locationInView:self];
        NSLog(@"Touch began at point %@", NSStringFromCGPoint(touchPoint));
	}
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
	for (UITouch *touch in touches) {
		CGPoint touchPoint = [touch locationInView:self];
		NSLog(@"Touch moved to point %@", NSStringFromCGPoint(touchPoint));
	}
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
	for (UITouch *touch in touches) {
		CGPoint touchPoint = [touch locationInView:self];
		NSLog(@"Touch ended at point %@", NSStringFromCGPoint(touchPoint));
	}
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
	NSLog(@"Touches cancelled");
}

手势识别

除了基本的触摸事件外,iOS系统还提供了一套手势识别机制。通过手势识别,我们可以轻松实现各种复杂的交互效果,例如拖动、缩放、旋转等。iOS系统内置了多种手势,包括点击、双击、长按、拖动、捏合、旋转等。

iOS系统提供了许多常用的手势识别器,包括:

  • UITapGestureRecognizer:敲击手势。
  • UIPinchGestureRecognizer:缩放手势。
  • UIRotationGestureRecognizer:旋转手势。
  • UISwipeGestureRecognizer:轻扫手势。
  • UIPanGestureRecognizer:拖拽手势。
  • UILongPressGestureRecognizer:长按手势。

响应手势

要响应手势,我们需要创建一个手势识别器,并将其添加到视图上。手势识别器会根据用户的操作自动触发相应的方法。

下面是一些常用的手势识别器及其使用示例:

点击手势识别器

1
2
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self.view addGestureRecognizer:tapGesture];

双击手势识别器

1
2
3
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
doubleTapGesture.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTapGesture];

长按手势识别器

1
2
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
[self.view addGestureRecognizer:longPressGesture];

拖动手势识别器

1
2
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.view addGestureRecognizer:panGesture];

捏合手势识别器

1
2
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
[self.view addGestureRecognizer:pinchGesture];

旋转手势识别器

1
2
UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
[self.view addGestureRecognizer:rotationGesture];

以下示例创建了一个拖动手势识别器,并将其添加到视图上。每当用户进行拖动操作时,handlePan:方法会被自动调用。在该方法中,我们获取手势的移动距离,并将视图的位置偏移相应的距离,从而实现了拖动效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建一个拖动手势识别器
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    
    // 将手势识别器添加到视图上
    [self.view addGestureRecognizer:panGesture];
}

- (void)handlePan:(UIPanGestureRecognizer *)gesture {
    CGPoint translation = [gesture translationInView:self.view];
    gesture.view.center = CGPointMake(gesture.view.center.x + translation.x, gesture.view.center.y + translation.y);
    [gesture setTranslation:CGPointZero inView:self.view];
}

自定义手势识别器

除了系统提供的手势识别器外,我们还可以自定义手势识别器来实现各种复杂的手势操作,例如双指旋转、三指拖拽等。自定义手势识别器需要继承UIGestureRecognizer类,并重写几个方法:

  • touchesBegan:withEvent::手指开始触摸屏幕时调用的方法。
  • touchesMoved:withEvent::手指在屏幕上移动时调用的方法。
  • touchesEnded:withEvent::手指离开屏幕时调用的方法。
  • touchesCancelled:withEvent::由于某种原因(比如来电)导致触摸事件取消时调用的方法。
  • reset:重置手势状态的方法。
  • shouldBeRequiredToFailByGestureRecognizer::返回一个布尔值,表示本手势是否应该由参数手势识别器阻止。
  • shouldReceiveTouch::返回一个布尔值,表示本手势是否应该接收指定的触摸事件。

以下示例演示如何使用复合手势识别器实现旋转缩放效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建一个捏合手势识别器
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
    [self.view addGestureRecognizer:pinchGesture];

    // 创建一个旋转手势识别器
    UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
    [self.view addGestureRecognizer:rotationGesture];
}

- (void)handlePinch:(UIPinchGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateChanged) {
        gesture.view.transform = CGAffineTransformScale(gesture.view.transform, gesture.scale, gesture.scale);
        gesture.scale = 1;
    }
}

- (void)handleRotation:(UIRotationGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateChanged) {
        gesture.view.transform = CGAffineTransformRotate(gesture.view.transform, gesture.rotation);
        gesture.rotation = 0;
    }
}

图片浏览器示例

在这个示例中,我们将实现以下功能:

  • 双击放大/缩小图片。
  • 拖拽图片查看不同部位。
  • 双指捏合缩放图片。
  • 单击隐藏/显示导航栏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#import "ViewController.h"


@interface ViewController ()<UIScrollViewDelegate>

@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.navigationItem.title = @"hello";
    
    // 创建UIScrollView对象并设置其代理
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.delegate = self;
    self.scrollView.maximumZoomScale = 3.0f;
    self.scrollView.minimumZoomScale = 1.0f;
    [self.view addSubview:self.scrollView];
    
    // 创建UIImageView对象并添加到UIScrollView上
    UIImage *image = [UIImage imageNamed:@"image.png"];
    self.imageView = [[UIImageView alloc] initWithImage:image];
    self.imageView.userInteractionEnabled = YES;
    [self.scrollView addSubview:self.imageView];
        
    // 添加单击手势识别器
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
    [self.imageView addGestureRecognizer:tapGesture];
    
    // 添加双击手势识别器
    UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapAction:)];
    doubleTapGesture.numberOfTapsRequired = 2;
    [self.imageView addGestureRecognizer:doubleTapGesture];
    
    // 添加拖拽手势识别器和捏合手势识别器
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
    [self.imageView addGestureRecognizer:panGesture];
    
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAction:)];
    [self.imageView addGestureRecognizer:pinchGesture];
}



#pragma mark - UIScrollViewDelegate

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
}

#pragma mark - Gesture Recognizer Actions

// 响应单击手势
- (void)tapAction:(UITapGestureRecognizer *)gesture {
    BOOL hidden = !self.navigationController.navigationBarHidden;
    [self.navigationController setNavigationBarHidden:hidden animated:YES];
}

- (void)doubleTapAction:(UITapGestureRecognizer *)gesture {
    CGFloat zoomScale = self.scrollView.zoomScale;
    if (zoomScale == 1.0f) {
        zoomScale = 2.0f;
    } else {
        zoomScale = 1.0f;
    }
    CGRect zoomRect = [self zoomRectForScale:zoomScale withCenter:[gesture locationInView:gesture.view]];
    [self.scrollView zoomToRect:zoomRect animated:YES];
}

- (void)panAction:(UIPanGestureRecognizer *)gesture {
    CGPoint translation = [gesture translationInView:self.imageView];
    CGPoint center = self.imageView.center;
    center.x += translation.x;
    center.y += translation.y;
    self.imageView.center = center;
    [gesture setTranslation:CGPointZero inView:self.imageView];
}

- (void)pinchAction:(UIPinchGestureRecognizer *)gesture {
    CGFloat scale = gesture.scale;
    self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale);
    gesture.scale = 1.0f;
}

#pragma mark - Helper Methods

- (CGRect)zoomRectForScale:(CGFloat)scale withCenter:(CGPoint)center {
    CGRect zoomRect;
    zoomRect.size.height = self.scrollView.frame.size.height / scale;
    zoomRect.size.width = self.scrollView.frame.size.width / scale;
    zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
    zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
    return zoomRect;
}

@end
本文由作者按照 CC BY 4.0 进行授权

iOS开发:物理引擎UIKit Dynamics

iOS开发:使用Core Graphics绘图和图像处理