iOS开发基础29-触摸事件及手势识别
在iOS应用中,用户的各种操作会产生多种事件。了解并掌握这些事件的处理方式对于构建高质量的iOS应用至关重要。本文将详细介绍iOS中的三大类型事件、响应者对象、触摸事件的处理、事件的产生与传递以及常见手势识别的处理方式。
一、iOS中的事件类型
iOS中的事件可以分为三大类型:
- 触摸事件:用户通过触摸屏幕产生的事件。
- 加速计事件:设备加速度的变化产生的事件。
- 远程控制事件:如控制音乐播放等产生的事件。
二、响应者对象(Responder Object)
在iOS中,不是所有对象都能处理事件,只有继承了UIResponder
的对象才能接收和处理事件,这些对象被称为“响应者对象”。常见的UIResponder子类有:
UIApplication
UIViewController
UIView
这些类都能够接收并处理事件。
三、UIResponder事件处理方法
UIResponder内部提供了处理不同类型事件的方法:
触摸事件方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
加速计事件方法
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
远程控制事件方法
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
四、UIView的触摸事件处理
触摸事件方法
作为UIResponder
的子类,UIView
可以覆盖以下方法来处理触摸事件:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
触摸事件的处理
以下是一个基本的示例:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"触摸开始");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"触摸移动");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"触摸结束");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"触摸取消");
}
五、UITouch对象
当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch
对象。每根手指对应一个UITouch
对象。
UITouch属性
window
:触摸产生时所处的窗口view
:触摸产生时所处的视图tapCount
:触摸的次数timestamp
:触摸事件的时间戳phase
:触摸事件的状态
UITouch方法
- (CGPoint)locationInView:(UIView *)view
:返回触摸在指定视图中的位置- (CGPoint)previousLocationInView:(UIView *)view
:返回前一个触摸点在指定视图中的位置
六、UIEvent对象
每产生一个事件,就会创建一个UIEvent
对象,记录事件产生的时刻和类型。
常见属性
type
:事件类型subtype
:事件子类型timestamp
:事件时间戳
UIEvent的方法
- (NSSet<UITouch *> *)touchesForView:(UIView *)view
:获取指定视图上的触摸对象
七、事件的产生和传递
事件的产生
触摸事件发生后,系统会将该事件加入到由UIApplication
管理的事件队列中,UIApplication会从事件队列中取出事件,并分发给应用程序的主窗口(keyWindow
),主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。
事件的传递
触摸事件的传递是从父控件传递到子控件,最终找到合适的视图处理事件。
以下是hitTest:withEvent:
方法的伪代码示例:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
if ([self pointInside:point withEvent:event] == NO) return nil;
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
该方法判断控件是否能接收事件以及触摸点是否在控件上,如果控件不能接收事件或者触摸点不在控件上,直接返回nil
。否则,从后往前遍历子控件数组,直到找到最合适的视图。
八、响应者链条
响应者链条的示意图
响应者链条是由多个响应者对象连接起来的链条。它能够让一个事件被多个对象处理。
事件传递的完整过程
- 先将事件对象由上往下传递,找到最合适的视图控件来处理事件。
- 调用最合适控件的
touches…
方法。 - 如果调用了
[super touches…]
,就会将事件顺着响应者链条向上传递。 - 继续调用上一个响应者的
touches…
方法。
判断上一个响应者
- 如果当前视图是控制器的视图,那么控制器是上一个响应者。
- 如果当前视图不是控制器的视图,那么父控件是上一个响应者。
- 如果最顶级的视图也不能处理事件,则事件传递给
window
对象,再传递给UIApplication
对象。
九、手势识别
iOS 3.2之后,苹果推出了手势识别功能,大大简化了触摸事件的处理。
常见手势识别器
UITapGestureRecognizer
(点按)UIPinchGestureRecognizer
(捏合)UIPanGestureRecognizer
(拖拽)UISwipeGestureRecognizer
(轻扫)UIRotationGestureRecognizer
(旋转)UILongPressGestureRecognizer
(长按)
手势识别的使用步骤
以UITapGestureRecognizer
为例:
// 1. 创建手势识别器对象
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
// 2. 设置手势识别器的属性
tap.numberOfTapsRequired = 2; // 连续敲击两次
tap.numberOfTouchesRequired = 1; // 使用一根手指
// 3. 添加手势识别器到对应的视图
[self.view addGestureRecognizer:tap];
// 4. 实现手势识别方法
- (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer {
NSLog(@"Tap gesture recognized");
}
其他手势识别器示例
长按手势识别(UILongPressGestureRecognizer
)
- (void)setUpLongPress {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[self.view addGestureRecognizer:longPress];
}
- (void)longPress:(UILongPressGestureRecognizer *)longPress {
if (longPress.state == UIGestureRecognizerStateEnded) {
NSLog(@"长按手势识别结束");
}
}
轻扫手势识别(UISwipeGestureRecognizer
)
- (void)setUpSwipe {
UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeRight];
UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipeLeft];
}
- (void)handleSwipe:(UISwipeGestureRecognizer *)swipe {
if (swipe.direction == UISwipeGestureRecognizerDirectionRight) {
NSLog(@"向右轻扫");
} else if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) {
NSLog(@"向左轻扫");
}
}
旋转手势识别(UIRotationGestureRecognizer
)
- (void)setUpRotation {
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];
[self.view addGestureRecognizer:rotation];
}
- (void)handleRotation:(UIRotationGestureRecognizer *)rotation {
self.view.transform = CGAffineTransformRotate(self.view.transform, rotation.rotation);
rotation.rotation = 0;
}
捏合手势识别(UIPinchGestureRecognizer
)
- (void)setUpPinch {
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
[self.view addGestureRecognizer:pinch];
}
- (void)handlePinch:(UIPinchGestureRecognizer *)pinch {
self.view.transform = CGAffineTransformScale(self.view.transform, pinch.scale, pinch.scale);
pinch.scale = 1;
}
拖拽手势识别(UIPanGestureRecognizer
)
- (void)setUpPan {
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.view addGestureRecognizer:pan];
}
- (void)handlePan:(UIPanGestureRecognizer *)pan {
CGPoint translation = [pan translationInView:self.view];
self.view.transform = CGAffineTransformTranslate(self.view.transform, translation.x, translation.y);
[pan setTranslation:CGPointZero inView:self.view];
}
多手势同时识别
可以通过实现UIGestureRecognizerDelegate
协议方法来支持多手势同时识别:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
手势识别状态
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible,
UIGestureRecognizerStateBegan,
UIGestureRecognizerStateChanged,
UIGestureRecognizerStateEnded,
UIGestureRecognizerStateCancelled,
UIGestureRecognizerStateFailed,
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};