手势识别
-
所有的手势操作都继承于UIGestureRecognizer,这个类本身是一个虚类,不能直接使用。这个类中定义了这几种手势共有的一些属性和方法(下表仅列出常用属性和方法):
名称 | 说明 |
---|---|
属性 | |
@property(nonatomic,readonly) UIGestureRecognizerState state; | 手势状态 |
@property(nonatomic, getter=isEnabled) BOOL enabled; | 手势是否可用 |
@property(nonatomic,readonly) UIView *view; | 触发手势的视图(一般在触摸执行操作中我们可以通过此属性获得触摸视图进行操作) |
@property(nonatomic) BOOL delaysTouchesBegan; | 手势识别失败前不执行触摸开始事件,默认为NO;如果为YES,那么成功识别则不执行触摸开始事件,失败则执行触摸开始事件;如果为NO,则不管成功与否都执行触摸开始事件; |
方法 | |
- (void)addTarget:(id)target action:(SEL)action; | 添加触摸执行事件 |
- (void)removeTarget:(id)target action:(SEL)action; | 移除触摸执行事件 |
- (NSUInteger)numberOfTouches; | 触摸点的个数(同时触摸的手指数) |
- (CGPoint)locationInView:(UIView*)view; | 在指定视图中的相对位置 |
- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView*)view; | 触摸点相对于指定视图的位置 |
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer; | 指定一个手势需要另一个手势执行失败才会执行 |
代理方法 | UIGestureRecognizerDelegate |
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; | 一个控件的手势识别后是否阻断手势识别继续向下传播,默认返回NO;如果为YES,响应者链上层对象触发手势识别后,如果下层对象也添加了手势并成功识别也会继续执行,否则上层对象识别后则不再继续传播; |
-
我们能直接使用的是UIGestureRecognizer类的子类,它的子类有:
手势 | 说明 |
---|---|
UITapGestureRecognizer | 点击手势 |
UIPinchGestureRecognizer | 捏合手势 |
UIPanGestureRecognizer | 拖动手势 |
UISwipeGestureRecognizer | 轻扫手势,支持四个方向,但多个方向需要添加多个手势 |
UIRotationGestureRecognizer | 旋转手势 |
UILongPressGestureRecognizer | 长按手势 |
-
手势的状态
- 在iOS中将手势状态分为如下几种:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible, // 尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态
UIGestureRecognizerStateBegan, // 手势已经开始,此时已经被识别,但是这个过程中可能发生变化,手势操作尚未完成
UIGestureRecognizerStateChanged, // 手势状态发生转变
UIGestureRecognizerStateEnded, // 手势识别操作完成(此时已经松开手指)
UIGestureRecognizerStateCancelled, // 手势被取消,恢复到默认状态
UIGestureRecognizerStateFailed, // t手势识别失败,恢复到默认状态
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // 手势识别完成,同UIGestureRecognizerStateEnded
};
- 在iOS中添加手势比较简单,可以归纳为以下几个步骤:
- 创建对应的手势对象;
- 设置手势识别属性【可选】;
- 附加手势到指定的对象;
- 编写手势操作方法;
参考【iOS_成才录】的【iOS --苹果自带的UIMenuController功能扩展】自定义了Label,给Label加上UIMenuController菜单栏,这里就用到了点击手势。
#import "QSLabel.h"
@implementation QSLabel
/**
* 初始化
*/
- (void)awakeFromNib
{
[super awakeFromNib];
// 添加手势
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(theLabelClick)];
[self addGestureRecognizer:tap];
[tap setNumberOfTapsRequired:2];
// 这里一定要设置为YES,不然接收不到手势事件
self.userInteractionEnabled = YES;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// 添加手势
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(theLabelClick)];
[self addGestureRecognizer:tap];
[tap setNumberOfTapsRequired:2];
self.userInteractionEnabled = YES;
}
return self;
}
/**
* 点击label
*/
- (void)theLabelClick
{
// 成为第一响应者
[self becomeFirstResponder];
// 创建菜单
UIMenuController *menuController = [UIMenuController sharedMenuController];
// 设置菜单显示的内容
menuController.menuItems = @[
[[UIMenuItem alloc] initWithTitle:@"复制" action:@selector(myCopy:)],
[[UIMenuItem alloc] initWithTitle:@"剪切" action:@selector(myCut:)],
[[UIMenuItem alloc] initWithTitle:@"粘贴" action:@selector(myPaste:)]
];
// 设置菜单显示的位置
[menuController setTargetRect:self.bounds inView:self];
// 显示菜单
[menuController setMenuVisible:YES animated:YES];
}
#pragma mark - UIMenuController相关
/**
* 让label具备第一响应者的资格
*/
- (BOOL)canBecomeFirstResponder
{
return YES;
}
/**
* 通过第一响应者的这个方法告诉UIMenuController可以显示什么内容
*/
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if ((action == @selector(myCopy:) && self.text)
|| (action == @selector(myCut:) && self.text)
|| (action == @selector(myPaste:))) {
return YES;
}
return NO;
}
#pragma mark - 监听MenuItem的点击事件
/**
* 复制
*/
- (void)myCopy:(UIMenuController *)menu
{
// 将文字存储到剪贴板
[UIPasteboard generalPasteboard].string = self.text;
}
/**
* 剪切
*/
- (void)myCut:(UIMenuController *)menu
{
// 将文字存储到剪贴板
[UIPasteboard generalPasteboard].string = self.text;
// 删除文字
self.text = nil;
}
/**
* 粘贴
*/
- (void)myPaste:(UIMenuController *)menu
{
self.text = [UIPasteboard generalPasteboard].string;
}
@end
-
手势冲突
同一个控件添加多个手势的时候,有时就会产生冲突,例如:轻扫和拖动;长按和拖动等
在iOS中,如果一个手势A的识别部分是另一个手势B的子部分时,默认情况下A就会先识别,B就无法识别了。要解决这个冲突可以利用- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;方法来完成。正是前面表格中UIGestureRecognizer的最后一个方法,这个方法可以指定某个手势执行的前提是另一个手势失败才会识别执行。也就是说如果我们指定拖动手势的执行前提为轻扫手势失败才执行,这样一来当我们手指轻轻滑动时,系统会优先考虑轻扫手势,如果最后发现该操作不是轻扫,那么才会执行拖动。
-
两个不同的控件上的手势同时执行
有时,同一个手势我们需要多个控件同时执行。
我们知道在iOS的触摸事件中,事件触发是根据响应者链进行的,上层触摸事件执行后就不再向下传播。默认情况下手势也是类似的,先识别的手势会阻断手势识别操作继续传播。那么如何让两个有层次关系并且都添加了手势的控件都能正确识别手势呢?答案就是利用代理的-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer方法。这个代理方法默认返回NO,会阻断继续向下识别手势,如果返回YES则可以继续向下传播识别。