iOS触摸事件与手势处理全解析
在iOS开发中,触摸事件和手势处理是非常重要的一部分,它直接影响着用户与应用程序的交互体验。下面我们将详细探讨iOS中触摸事件的转发、多点触摸架构、触摸通知方法,以及通过两个具体的应用实例来加深理解。
事件转发:保持响应链的活跃
在iOS应用中,响应链是处理触摸事件的关键机制。以邮件应用中的表格视图单元格为例,假设该单元格只处理删除滑动手势。当触摸事件发生时,单元格需要判断该事件是否为其能处理的滑动手势。
-
事件匹配
:如果事件匹配单元格所期望的滑动手势,单元格会采取相应动作,事件处理结束。
-
事件不匹配
:若事件不匹配,单元格有责任将该事件手动转发给响应链中的下一个对象。否则,表格及响应链上的其他对象将无法响应,应用可能无法按用户预期运行。
以下是一个虚构的事件响应方法示例,展示了如何将事件转发给下一个响应者:
- (void)respondToFictionalEvent:(UIEvent *)event {
if ([self shouldHandleEvent:event]) {
[self handleEvent:event];
} else {
[[self nextResponder] respondToFictionalEvent:event];
}
}
在实际开发中,当对象拦截到自己无法处理的事件时,应手动将其传递给下一个响应者,以确保响应链的正常工作。
多点触摸架构
手势通过事件嵌入在响应链中传递,因此处理多点触摸屏幕交互的代码需要包含在响应链中的对象里。通常,我们可以选择将代码嵌入
UIView
的子类或
UIViewController
中。
-
代码嵌入视图
:如果视图需要根据用户触摸对自身进行操作,代码可能应放在定义该视图的类中。例如,
UISwitch
和
UISlider
等控件类会响应触摸相关事件,它们的手势处理代码就嵌入在类中。
-
代码嵌入视图控制器
:当处理的手势影响的不仅仅是被触摸的对象时,手势代码应放在相关的视图控制器类中。例如,用户触摸一行并指示删除所有行的手势,应由视图控制器中的代码处理。
四个触摸通知方法
iOS系统提供了四个方法来通知响应者关于触摸事件:
1.
touchesBegan:withEvent:
:当用户首次触摸屏幕时,系统会查找实现了该方法的响应者。通过该方法,我们可以获取触摸的点击次数和触摸数量。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSUInteger numTaps = [[touches anyObject] tapCount];
NSUInteger numTouches = [event.allTouches count];
// Do something here.
}
-
touchesMoved:withEvent::当用户手指在屏幕上移动时,该方法会被多次调用。我们可以通过它获取每个手指的当前位置和之前的位置。 -
touchesEnded:withEvent::当用户的任何手指离开屏幕时,调用该方法,表示手势结束。 -
touchesCancelled:withEvent::如果用户在手势进行中被其他事件中断(如来电),该方法会被调用,可在此进行清理工作。
在这些方法中,
allTouches
属性包含当前所有按压在屏幕上的手指对应的
UITouch
对象,而
touches
参数包含刚刚添加、移除、移动或停止移动的手指对应的
UITouch
对象。
TouchExplorer应用实践
为了更好地理解四个触摸通知方法的调用时机,我们可以创建一个名为
TouchExplorer
的应用。
操作步骤如下
:
1. 在Xcode中,使用单视图应用模板创建新项目,产品名称为
TouchExplorer
,设备选择
Universal
。
2. 在
ViewController.m
的类扩展中添加三个输出口:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
@property (weak, nonatomic) IBOutlet UILabel *tapsLabel;
@property (weak, nonatomic) IBOutlet UILabel *touchesLabel;
@end
-
编辑
Main.storyboard:-
拖动一个标签到视图的左上角,按住
Option键复制两个标签,使它们垂直排列。 -
设置标签的自动布局约束:在文档大纲中,从第一个标签
Control拖动到主视图,按住Shift键选择“Top Space to Top Layout Guide”和“Leading Space to Container Margin”,对其他两个标签执行相同操作。 -
将标签连接到对应的输出口:从视图控制器图标
Control拖动到三个标签,分别连接到messageLabel、tapsLabel和touchesLabel。 -
双击每个标签并按
Delete键清除文本。
-
拖动一个标签到视图的左上角,按住
- 确保视图的用户交互和多点触摸功能启用:单击视图背景或文档大纲中的视图图标,打开属性检查器,在视图部分勾选“User Interaction Enabled”和“Multiple Touch”。
-
在
ViewController.m的@implementation部分添加以下代码:
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)updateLabelsFromTouches:(NSSet *)touches {
NSUInteger numTaps = [[touches anyObject] tapCount];
NSString *tapsMessage = [[NSString alloc]
initWithFormat:@"%ld taps detected", (unsigned long)numTaps];
self.tapsLabel.text = tapsMessage;
NSUInteger numTouches = [touches count];
NSString *touchMsg = [[NSString alloc] initWithFormat:
@"%ld touches detected", (unsigned long)numTouches];
self.touchesLabel.text = touchMsg;
}
#pragma mark - Touch Event Methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.messageLabel.text = @"Touches Began";
[self updateLabelsFromTouches:event.allTouches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
self.messageLabel.text = @"Touches Cancelled";
[self updateLabelsFromTouches: event.allTouches];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.messageLabel.text = @"Touches Ended.";
[self updateLabelsFromTouches: event.allTouches];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
self.messageLabel.text = @"Drag Detected";
[self updateLabelsFromTouches: event.allTouches];
}
@end
编译并运行应用,在模拟器中,我们可以通过多次点击屏幕增加点击次数,点击并按住鼠标拖动来模拟触摸和拖动。同时,按住
Option
键并点击鼠标拖动可以模拟双指捏合,按住
Option
和
Shift
键可以模拟双指滑动。如果在真机上运行,我们可以尝试不同数量的手指触摸和点击,以更好地理解四个触摸方法的工作原理。
Swipes应用:检测滑动手势
接下来,我们将创建一个名为
Swipes
的应用,用于检测水平和垂直滑动手势。
操作步骤如下
:
1. 在Xcode中,使用单视图应用模板创建新项目,设备选择
Universal
,项目名称为
Swipes
。
2. 在
ViewController.m
的类扩展中添加以下代码:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (nonatomic) CGPoint gestureStartPoint;
@end
-
编辑
Main.storyboard:- 确保视图控制器的视图启用了用户交互和多点触摸功能。
- 从库中拖动一个标签到视图的上部,设置文本对齐方式为居中,并调整其他文本属性以方便阅读。
-
在文档大纲中,从标签
Control拖动到视图,按住Shift键选择“Top Space to Top Layout Guide”和“Center Horizontally in Container”。 -
从视图控制器图标
Control拖动到标签,连接到label输出口。 - 双击标签并删除其文本。
-
在
ViewController.m中添加以下代码:
static CGFloat const kMinimumGestureLength = 25;
static CGFloat const kMaximumVariance = 5;
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.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(self.gestureStartPoint.x - currentPosition.x);
CGFloat deltaY = fabsf(self.gestureStartPoint.y - currentPosition.y);
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 = @""; });
}
}
@end
在
touchesBegan:withEvent:
方法中,我们记录用户首次触摸的位置。在
touchesMoved:withEvent:
方法中,计算手指在水平和垂直方向上的移动距离,判断是否构成滑动手势。如果是,设置标签文本显示检测到的滑动类型,并在2秒后清除文本。
通过以上两个应用实例,我们可以更深入地理解iOS中触摸事件和手势处理的机制,从而在实际开发中更好地实现用户交互功能。
通过以上步骤,我们可以创建一个简单的iOS应用,用于检测水平和垂直滑动手势。在实际开发中,我们可以根据需求调整最小手势长度和最大偏差值,以实现更精确的手势检测。
深入剖析与总结
触摸事件与手势处理的技术要点回顾
为了更清晰地理解iOS中触摸事件和手势处理的关键技术点,我们对前面的内容进行总结和深入剖析。
| 技术要点 | 描述 |
|---|---|
| 事件转发 |
当对象无法处理触摸事件时,需要手动将其转发给响应链中的下一个对象,以确保事件能被正确处理。示例代码:
objc<br>- (void)respondToFictionalEvent:(UIEvent *)event {<br> if ([self shouldHandleEvent:event]) {<br> [self handleEvent:event];<br> } else {<br> [[self nextResponder] respondToFictionalEvent:event];<br> }<br>}<br>
|
| 多点触摸架构 |
处理多点触摸交互的代码可嵌入
UIView
子类或
UIViewController
中。视图自身操作相关代码放视图类,影响多个对象的手势代码放视图控制器类。
|
| 触摸通知方法 |
包括
touchesBegan:withEvent:
、
touchesMoved:withEvent:
、
touchesEnded:withEvent:
和
touchesCancelled:withEvent:
。通过这些方法可以获取触摸的点击次数、触摸数量、手指位置等信息。
|
| 手势检测 |
以
Swipes
应用为例,通过记录触摸起始点,在手指移动时计算水平和垂直方向的移动距离,判断是否构成滑动手势。
|
代码逻辑流程图
下面是
Swipes
应用中滑动手势检测的逻辑流程图:
graph TD;
A[用户触摸屏幕] --> B[touchesBegan:withEvent:记录起始点];
B --> C[手指移动];
C --> D[touchesMoved:withEvent:计算移动距离];
D --> E{是否构成滑动手势};
E -- 是 --> F[显示滑动类型标签文本];
F --> G[2秒后清除标签文本];
E -- 否 --> C;
实际开发中的注意事项
- 响应链的维护 :在开发过程中,要时刻注意响应链的完整性。如果某个对象错误地拦截了事件而不进行转发,可能会导致应用的部分功能无法正常使用。例如,在自定义视图时,要确保在不需要处理某些事件时,将其正确地传递给下一个响应者。
-
手势检测的精度
:在
Swipes应用中,我们定义了最小手势长度和最大偏差值。在实际开发中,需要根据应用的具体需求进行调整。如果最小手势长度设置得太小,可能会误判一些小的移动为滑动手势;如果设置得太大,用户可能需要滑动很长距离才能触发手势。 - 真机测试的重要性 :虽然模拟器可以模拟一些基本的触摸操作,但要体验完整的多点触摸功能,必须在真机上进行测试。真机的触摸响应更加真实,能够发现一些在模拟器中无法发现的问题。
拓展应用场景
- 游戏开发 :在游戏中,触摸事件和手势处理是实现玩家交互的关键。例如,通过检测滑动手势可以控制角色的移动方向,通过点击手势可以触发角色的攻击动作。
-
绘图应用
:绘图应用需要精确地处理用户的触摸事件,以实现画笔的移动、颜色的选择等功能。可以利用
touchesMoved:withEvent:方法实时获取手指的位置,绘制线条和图形。 - 导航应用 :在导航应用中,用户可能会通过双指捏合来缩放地图,通过滑动手势来移动地图。通过实现相应的手势检测代码,可以为用户提供更加便捷的操作体验。
总结
iOS中的触摸事件和手势处理是实现用户交互的重要组成部分。通过理解事件转发、多点触摸架构、触摸通知方法和手势检测的原理,并结合实际应用的开发,我们可以为用户提供更加流畅、便捷的交互体验。在实际开发中,要注意响应链的维护、手势检测的精度和真机测试的重要性,同时可以根据不同的应用场景进行拓展和创新。希望本文能够帮助开发者更好地掌握iOS触摸事件和手势处理的技术,开发出更加优秀的应用程序。
超级会员免费看
70

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



