iOS 手势识别:从基础到高级应用
1. 手动检测滑动手势
在开发中,我们可以通过代码手动检测滑动手势。以下是一段检测水平和垂直滑动的代码示例:
if (deltaX >=kMinimumGestureLength && deltaY <= kMaximumVariance) {
self.label.text = @"Horizontal swipe detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.label.text = @""; });
} else if (deltaY >=kMinimumGestureLength &&
deltaX <= kMaximumVariance){
self.label.text = @"Vertical swipe detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.label.text = @""; });
}
操作步骤如下:
1. 编写上述代码来判断滑动方向。
2. 编译并运行应用程序。
3. 如果点击和拖动没有可见结果,耐心尝试垂直或水平拖动,直到掌握滑动操作。
2. 自动手势识别
iOS 提供了
UIGestureRecognizer
类来简化手势识别的过程。以滑动手势为例,我们可以使用
UISwipeGestureRecognizer
来实现。
2.1 修改 Swipes 应用
步骤如下:
1. 复制 Swipes 项目文件夹。
2. 打开
ViewController.m
,删除
touchesBegan:withEvent:
和
touchesMoved:withEvent:
方法。
3. 添加以下新方法:
- (void)reportHorizontalSwipe:(UIGestureRecognizer *)recognizer {
self.label.text = @"Horizontal swipe detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.label.text = @""; });
}
- (void)reportVerticalSwipe:(UIGestureRecognizer *)recognizer {
self.label.text = @"Vertical swipe detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.label.text = @""; });
}
-
修改
viewDidLoad方法:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
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];
}
-
可以进一步删除
ViewController.m中gestureStartPoint属性的声明和两个常量值。 - 构建并运行应用程序,测试新的手势识别器。
2.2 两种方法对比
| 方法 | 代码复杂度 | 理解难度 | 扩展性 |
|---|---|---|---|
| 手动检测 | 较高 | 较难 | 较差 |
| 手势识别器 | 较低 | 较易 | 较好 |
使用手势识别器的代码更易于理解和编写,而且 Apple 的手势识别系统具有可扩展性。
3. 实现多手指滑动
在 Swipes 应用中,之前只处理了单手指滑动。现在我们可以使用手势识别器来处理多手指滑动。
3.1 修改 Swipes 项目
步骤如下:
1. 再次复制 Swipes 项目文件夹。
2. 编辑
ViewController.m
,修改
viewDidLoad
方法:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the
// view, typically from a nib.
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];
}
}
-
添加
descriptionForTouchCount:方法:
- (NSString *)descriptionForTouchCount:(NSUInteger)touchCount {
switch (touchCount) {
case 1:
return @"Single";
case 2:
return @"Double";
case 3:
return @"Triple";
case 4:
return @"Quadruple";
case 5:
return @"Quintuple";
default:
return @"";
}
}
- 修改两个滑动报告方法:
- (void)reportHorizontalSwipe:(UIGestureRecognizer *)recognizer {
self.label.text = @"Horizontal swipe detected";
self.label.text = [NSString stringWithFormat:@"%@ Horizontal swipe detected",
[self descriptionForTouchCount:[recognizer numberOfTouches]]];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.label.text = @""; });
}
- (void)reportVerticalSwipe:(UIGestureRecognizer *)recognizer {
self.label.text = @"Vertical swipe detected";
self.label.text = [NSString stringWithFormat:@"%@ Vertical swipe detected",
[self descriptionForTouchCount:[recognizer numberOfTouches]]];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.label.text = @""; });
}
- 编译并运行应用程序,尝试触发不同手指数量的滑动。
3.2 注意事项
- 手指之间不要太靠近,否则可能会被识别为单手指触摸。
- 不建议在重要手势中使用四指或五指滑动,因为很多人手指太大,难以有效执行这些滑动。
- 在 iPad 上,一些四指和五指手势在系统级别默认开启,用于切换应用和返回主屏幕,可以在设置中关闭,但最好避免在自己的应用中使用这些手势。
4. 检测多次点击
在处理多次点击时,需要考虑不同点击次数触发不同操作的情况。
4.1 问题分析
当用户进行多次点击时,会收到多个点击通知。例如,用户进行三次点击,会依次收到单次点击、双次点击和三次点击的通知。如果要对双次点击和三次点击执行不同操作,需要解决通知冲突的问题。
4.2 解决方案
Apple 提供了一种机制,让多个手势识别器可以协同工作。通过设置一个手势识别器的触发条件,即只有在其他手势识别器无法触发时才触发自己的方法。
4.3 示例代码
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];
4.4 创建 TapTaps 项目
步骤如下:
1. 在 Xcode 中创建一个新的 Single View Application 项目,命名为 TapTaps,选择 Universal 设备。
2. 打开
ViewController.m
,修改类接口:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *singleLabel;
@property (weak, nonatomic) IBOutlet UILabel *doubleLabel;
@property (weak, nonatomic) IBOutlet UILabel *tripleLabel;
@property (weak, nonatomic) IBOutlet UILabel *quadrupleLabel;
@end
-
编辑
Main.storyboard,添加四个标签,设置文本对齐方式和自动布局约束,并将标签连接到相应的属性。 -
修改
ViewController.m中的代码:
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UITapGestureRecognizer *singleTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(singleTap)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:singleTap];
UITapGestureRecognizer *doubleTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(doubleTap)];
doubleTap.numberOfTapsRequired = 2;
doubleTap.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:doubleTap];
[singleTap requireGestureRecognizerToFail:doubleTap];
UITapGestureRecognizer *tripleTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tripleTap)];
tripleTap.numberOfTapsRequired = 3;
tripleTap.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:tripleTap];
[doubleTap requireGestureRecognizerToFail:tripleTap];
UITapGestureRecognizer *quadrupleTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(quadrupleTap)];
quadrupleTap.numberOfTapsRequired = 4;
quadrupleTap.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:quadrupleTap];
[tripleTap requireGestureRecognizerToFail:quadrupleTap];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)singleTap {
self.singleLabel.text = @"Single Tap Detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.singleLabel.text = @""; });
}
- (void)doubleTap {
self.doubleLabel.text = @"Double Tap Detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.doubleLabel.text = @""; });
}
- (void)tripleTap {
self.tripleLabel.text = @"Triple Tap Detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.tripleLabel.text = @""; });
}
- (void)quadrupleTap {
self.quadrupleLabel.text = @"Quadruple Tap Detected";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{ self.quadrupleLabel.text = @""; });
}
@end
4.5 代码解释
在
viewDidLoad
方法中,我们创建了四个点击手势识别器,分别处理单次点击、双次点击、三次点击和四次点击。通过设置
requireGestureRecognizerToFail
方法,确保每个手势识别器在合适的时机触发。
编译并运行应用程序,无论进行单次、双次、三次还是四次点击,最终只会显示一个标签,约一秒半后标签会自动清除,可以再次尝试点击。
5. 检测捏合和旋转手势
捏合和旋转是常见的手势,分别使用
UIPinchGestureRecognizer
和
UIRotationGestureRecognizer
进行检测。
5.1 创建 PinchMe 项目
步骤如下:
1. 在 Xcode 中创建一个新的 Single View Application 项目,命名为 PinchMe。
2. 将
yosemite - meadows.png
图片拖放到项目的
Images.xcassets
中。
3. 打开
ViewController.h
,修改代码:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UIGestureRecognizerDelegate>
@end
-
打开
ViewController.m,进行以下修改:
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) UIImageView *imageView;
@end
@implementation ViewController
CGFloat scale, previousScale;
CGFloat rotation, previousRotation;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
previousScale = 1;
UIImage *image = [UIImage imageNamed:@"yosemite-meadows"];
self.imageView = [[UIImageView alloc] initWithImage:image];
self.imageView.userInteractionEnabled = YES;
self.imageView.center = self.view.center;
[self.view addSubview:self.imageView];
UIPinchGestureRecognizer *pinchGesture =
[[UIPinchGestureRecognizer alloc]
initWithTarget:self action:@selector(doPinch:)];
pinchGesture.delegate = self;
[self.imageView addGestureRecognizer:pinchGesture];
UIRotationGestureRecognizer *rotationGesture =
[[UIRotationGestureRecognizer alloc]
initWithTarget:self action:@selector(doRotate:)];
rotationGesture.delegate = self;
[self.imageView addGestureRecognizer:rotationGesture];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (void)transformImageView {
CGAffineTransform t = CGAffineTransformMakeScale(scale * previousScale,
scale * previousScale);
t = CGAffineTransformRotate(t, rotation + previousRotation);
self.imageView.transform = t;
}
- (void)doPinch:(UIPinchGestureRecognizer *)gesture {
scale = gesture.scale;
[self transformImageView];
if (gesture.state == UIGestureRecognizerStateEnded) {
previousScale = scale * previousScale;
scale = 1;
}
}
- (void)doRotate:(UIRotationGestureRecognizer *)gesture {
rotation = gesture.rotation;
[self transformImageView];
if (gesture.state == UIGestureRecognizerStateEnded) {
previousRotation = rotation + previousRotation;
rotation = 0;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
5.2 代码解释
-
在
viewDidLoad方法中,创建了一个UIImageView用于显示图片,并添加了捏合和旋转手势识别器。 -
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法返回YES,允许捏合和旋转手势同时工作。 -
transformImageView方法根据当前的缩放和旋转值更新图片视图的变换。 -
doPinch:和doRotate:方法分别处理捏合和旋转手势,在手势结束时更新之前的缩放和旋转值。
通过以上步骤,我们可以实现对捏合和旋转手势的检测,并根据手势对图片进行缩放和旋转操作。
6. 手势识别总结
6.1 不同手势识别器对比
| 手势类型 | 对应识别器类 | 特点 |
|---|---|---|
| 滑动 |
UISwipeGestureRecognizer
| 可配置单指或多指滑动,检测水平和垂直方向滑动 |
| 点击 |
UITapGestureRecognizer
| 可配置点击次数,通过设置依赖关系避免点击冲突 |
| 捏合 |
UIPinchGestureRecognizer
| 连续手势识别器,根据手指距离改变缩放比例 |
| 旋转 |
UIRotationGestureRecognizer
| 连续手势识别器,根据手指旋转角度改变旋转值 |
6.2 手势识别流程
graph LR
A[用户进行手势操作] --> B{手势类型判断}
B --> |滑动| C(UISwipeGestureRecognizer)
B --> |点击| D(UITapGestureRecognizer)
B --> |捏合| E(UIPinchGestureRecognizer)
B --> |旋转| F(UIRotationGestureRecognizer)
C --> G[触发对应滑动方法]
D --> H[触发对应点击方法]
E --> I[触发捏合方法,更新缩放值]
F --> J[触发旋转方法,更新旋转值]
这个流程图展示了用户进行手势操作后,系统根据手势类型选择相应的手势识别器,并触发对应的处理方法。
6.3 开发建议
- 选择合适的识别器 :根据实际需求选择合适的手势识别器,避免手动处理复杂的手势逻辑。
-
处理冲突
:当存在多个手势识别器可能冲突时,使用
requireGestureRecognizerToFail方法进行处理。 - 考虑用户体验 :在设计手势时,要考虑用户的操作习惯和手指大小,避免使用过于复杂或难以执行的手势。
7. 实际应用案例分析
7.1 图片浏览应用
在图片浏览应用中,常见的手势有滑动切换图片、捏合缩放图片、旋转图片等。以下是一个简单的图片浏览应用的实现思路:
1.
滑动切换图片
:使用
UISwipeGestureRecognizer
检测水平滑动,根据滑动方向切换到上一张或下一张图片。
UISwipeGestureRecognizer *leftSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showNextImage)];
leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftSwipe];
UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(showPreviousImage)];
rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightSwipe];
-
捏合缩放图片
:使用
UIPinchGestureRecognizer检测捏合手势,根据缩放比例调整图片大小。
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
[self.imageView addGestureRecognizer:pinchGesture];
- (void)handlePinch:(UIPinchGestureRecognizer *)gesture {
CGFloat scale = gesture.scale;
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale);
gesture.scale = 1;
}
-
旋转图片
:使用
UIRotationGestureRecognizer检测旋转手势,根据旋转角度旋转图片。
UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
[self.imageView addGestureRecognizer:rotationGesture];
- (void)handleRotation:(UIRotationGestureRecognizer *)gesture {
CGFloat rotation = gesture.rotation;
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation);
gesture.rotation = 0;
}
7.2 地图应用
在地图应用中,滑动可以移动地图视野,捏合可以缩放地图级别,双指旋转可以旋转地图方向。以下是一个简单的地图应用的实现思路:
1.
滑动移动地图
:使用
UIPanGestureRecognizer
检测滑动手势,根据滑动距离移动地图视野。
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.mapView addGestureRecognizer:panGesture];
- (void)handlePan:(UIPanGestureRecognizer *)gesture {
CGPoint translation = [gesture translationInView:self.mapView];
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(self.mapView.centerCoordinate.latitude - translation.y / 1000, self.mapView.centerCoordinate.longitude + translation.x / 1000)];
[gesture setTranslation:CGPointZero inView:self.mapView];
}
-
捏合缩放地图
:使用
UIPinchGestureRecognizer检测捏合手势,根据缩放比例调整地图级别。
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
[self.mapView addGestureRecognizer:pinchGesture];
- (void)handlePinch:(UIPinchGestureRecognizer *)gesture {
CGFloat scale = gesture.scale;
MKZoomScale currentZoomScale = self.mapView.zoomScale;
self.mapView.zoomScale = currentZoomScale * scale;
gesture.scale = 1;
}
-
旋转地图
:使用
UIRotationGestureRecognizer检测旋转手势,根据旋转角度旋转地图方向。
UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
[self.mapView addGestureRecognizer:rotationGesture];
- (void)handleRotation:(UIRotationGestureRecognizer *)gesture {
CGFloat rotation = gesture.rotation;
self.mapView.camera.heading += rotation * 180 / M_PI;
gesture.rotation = 0;
}
通过以上案例可以看出,手势识别在实际应用中非常重要,可以大大提升用户体验。开发者可以根据不同的应用场景选择合适的手势识别器,并进行相应的处理。
8. 未来发展趋势
8.1 更复杂的手势支持
随着技术的发展,未来可能会支持更复杂的手势,如三指以上的复杂组合手势、动态手势等。这将为应用开发带来更多的创意和可能性。
8.2 与其他技术融合
手势识别可能会与虚拟现实(VR)、增强现实(AR)等技术融合,提供更加沉浸式的交互体验。例如,在 VR 应用中,用户可以通过手势进行物体的抓取、移动等操作。
8.3 智能化手势识别
未来的手势识别系统可能会更加智能化,能够根据用户的习惯和环境自动调整识别策略,提高识别的准确性和效率。
总之,手势识别技术在移动应用开发中具有重要的地位,并且有着广阔的发展前景。开发者需要不断学习和掌握新的手势识别技术,为用户提供更加优质的应用体验。
超级会员免费看
519

被折叠的 条评论
为什么被折叠?



