在iOS中添加手势比较简单,可以归纳为以下几个步骤:
- 创建对应的手势对象;
- 设置手势识别属性【可选】;
- 附加手势到指定的对象;
- 编写手势操作方法;
为了帮助大家理解,下面以一个图片查看程序演示一下上面几种手势,在这个程序中我们完成以下功能:
如果点按图片会在导航栏显示图片名称;
如果长按图片会显示删除按钮,提示用户是否删除;
如果捏合会放大、缩小图片;
如果轻扫会切换到下一张或上一张图片;
如果旋转会旋转图片;
如果拖动会移动图片;
具体布局草图如下:
为了显示导航条,我们首先将主视图控制器KCPhotoViewController放入一个导航控制器,然后在主视图控制器中放一个UIImage用于展示图片。下面是主要代码:
// // KCGestureViewController.m // TouchEventAndGesture // // Created by Kenshin Cui on 14-3-16. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCPhotoViewController.h" #define kImageCount 3 @interface KCPhotoViewController (){ UIImageView *_imageView;//图片展示控件 int _currentIndex;//当前图片索引 } @end @implementation KCPhotoViewController - (void)viewDidLoad { [super viewDidLoad]; [self initLayout]; [self addGesture]; } #pragma mark 布局 -(void)initLayout{ /*添加图片展示控件*/ CGSize screenSize=[UIScreen mainScreen].applicationFrame.size; CGFloat topPadding=20; CGFloat y=22+44+topPadding,height=screenSize.height-y-topPadding; CGRect imageFrame=CGRectMake(0, y, screenSize.width, height); _imageView=[[UIImageView alloc]initWithFrame:imageFrame]; _imageView.contentMode=UIViewContentModeScaleToFill;//设置内容模式为缩放填充 _imageView.userInteractionEnabled=YES;//这里必须设置为YES,否则无法接收手势操作 [self.view addSubview:_imageView]; //添加默认图片 UIImage *image=[UIImage imageNamed:@"0.jpg"]; _imageView.image=image; [self showPhotoName]; } #pragma mark 添加手势 -(void)addGesture{ /*添加点按手势*/ //创建手势对象 UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)]; //设置手势属性 tapGesture.numberOfTapsRequired=1;//设置点按次数,默认为1,注意在iOS中很少用双击操作 tapGesture.numberOfTouchesRequired=1;//点按的手指数 //添加手势到对象(注意,这里添加到了控制器视图中,而不是图片上,否则点击空白无法隐藏导航栏) [self.view addGestureRecognizer:tapGesture]; /*添加长按手势*/ UILongPressGestureRecognizer *longPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressImage:)]; longPressGesture.minimumPressDuration=0.5;//设置长按时间,默认0.5秒,一般这个值不要修改 //注意由于我们要做长按提示删除操作,因此这个手势不再添加到控制器视图上而是添加到了图片上 [_imageView addGestureRecognizer:longPressGesture]; /*添加捏合手势*/ UIPinchGestureRecognizer *pinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchImage:)]; [self.view addGestureRecognizer:pinchGesture]; /*添加旋转手势*/ UIRotationGestureRecognizer *rotationGesture=[[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotateImage:)]; [self.view addGestureRecognizer:rotationGesture]; /*添加拖动手势*/ UIPanGestureRecognizer *panGesture=[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panImage:)]; [_imageView addGestureRecognizer:panGesture]; /*添加轻扫手势*/ //注意一个轻扫手势只能控制一个方向,默认向右,通过direction进行方向控制 UISwipeGestureRecognizer *swipeGestureToRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; //swipeGestureToRight.direction=UISwipeGestureRecognizerDirectionRight;//默认为向右轻扫 [self.view addGestureRecognizer:swipeGestureToRight]; UISwipeGestureRecognizer *swipeGestureToLeft=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; swipeGestureToLeft.direction=UISwipeGestureRecognizerDirectionLeft; [self.view addGestureRecognizer:swipeGestureToLeft]; } #pragma mark 显示图片名称 -(void)showPhotoName{ NSString *title=[NSString stringWithFormat:@"%i.jpg",_currentIndex]; [self setTitle:title]; } #pragma mark 下一张图片 -(void)nextImage{ int index=(_currentIndex+kImageCount+1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName]; } #pragma mark 上一张图片 -(void)lastImage{ int index=(_currentIndex+kImageCount-1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName]; } #pragma mark - 手势操作 #pragma mark 点按隐藏或显示导航栏 -(void)tapImage:(UITapGestureRecognizer *)gesture{ //NSLog(@"tap:%i",gesture.state); BOOL hidden=!self.navigationController.navigationBarHidden; [self.navigationController setNavigationBarHidden:hidden animated:YES]; } #pragma mark 长按提示是否删除 -(void)longPressImage:(UILongPressGestureRecognizer *)gesture{ //NSLog(@"longpress:%i",gesture.state); //注意其实在手势里面有一个view属性可以获取点按的视图 //UIImageView *imageView=(UIImageView *)gesture.view; //由于连续手势此方法会调用多次,所以需要判断其手势状态 if (gesture.state==UIGestureRecognizerStateBegan) { UIActionSheet *actionSheet=[[UIActionSheet alloc]initWithTitle:@"System Info" delegate:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete the photo" otherButtonTitles:nil]; [actionSheet showInView:self.view]; } } #pragma mark 捏合时缩放图片 -(void)pinchImage:(UIPinchGestureRecognizer *)gesture{ //NSLog(@"pinch:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //捏合手势中scale属性记录的缩放比例 _imageView.transform=CGAffineTransformMakeScale(gesture.scale, gesture.scale); }else if(gesture.state==UIGestureRecognizerStateEnded){//结束后恢复 [UIView animateWithDuration:.5 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消一切形变 }]; } } #pragma mark 旋转图片 -(void)rotateImage:(UIRotationGestureRecognizer *)gesture{ //NSLog(@"rotate:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //旋转手势中rotation属性记录了旋转弧度 _imageView.transform=CGAffineTransformMakeRotation(gesture.rotation); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.8 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消形变 }]; } } #pragma mark 拖动图片 -(void)panImage:(UIPanGestureRecognizer *)gesture{ if (gesture.state==UIGestureRecognizerStateChanged) { CGPoint translation=[gesture translationInView:self.view];//利用拖动手势的translationInView:方法取得在相对指定视图(这里是控制器根视图)的移动 _imageView.transform=CGAffineTransformMakeTranslation(translation.x, translation.y); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.5 animations:^{ _imageView.transform=CGAffineTransformIdentity; }]; } } #pragma mark 轻扫则查看下一张或上一张 //注意虽然轻扫手势是连续手势,但是只有在识别结束才会触发,不用判断状态 -(void)swipeImage:(UISwipeGestureRecognizer *)gesture{ // NSLog(@"swip:%i",gesture.state); // if (gesture.state==UIGestureRecognizerStateEnded) { //direction记录的轻扫的方向 if (gesture.direction==UISwipeGestureRecognizerDirectionRight) {//向右 [self nextImage]; // NSLog(@"right"); }else if(gesture.direction==UISwipeGestureRecognizerDirectionLeft){//向左 // NSLog(@"left"); [self lastImage]; } // } } //-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ // //NSLog(@"touch begin..."); //} @end
运行效果:
在上面示例中需要强调几点:
- UIImageView默认是不支持交互的,也就是userInteractionEnabled=NO ,因此要接收触摸事件(手势识别),必须设置userInteractionEnabled=YES(在iOS中UILabel、UIImageView的userInteractionEnabled默认都是NO,UIButton、UITextField、UIScrollView、UITableView等默认都是YES)。
- 轻扫手势虽然是连续手势但是它的操作事件只会在识别结束时调用一次,其他连续手势都会调用多次,一般需要进行状态判断;此外轻扫手势支持四个方向,但是如果要支持多个方向需要添加多个轻扫手势。
手势冲突
细心的童鞋会发现在上面的演示效果图中当切换到下一张或者上一张图片时并没有轻扫图片而是在空白地方轻扫完成,原因是如果我轻扫图片会引起拖动手势而不是轻扫手势。换句话说,两种手势发生了冲突。
冲突的原因很简单,拖动手势的操作事件是在手势的开始状态(状态1)识别执行的,而轻扫手势的操作事件只有在手势结束状态(状态3)才能执行,因此轻扫手势就作为了牺牲品没有被正确识别。我们理想的情况当然是如果在图片上拖动就移动图片,如果在图片上轻扫就翻动图片。如何解决这个冲突呢?
在iOS中,如果一个手势A的识别部分是另一个手势B的子部分时,默认情况下A就会先识别,B就无法识别了。要解决这个冲突可以利用- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;方法来完成。正是前面表格中UIGestureRecognizer的最后一个方法,这个方法可以指定某个手势执行的前提是另一个手势失败才会识别执行。也就是说如果我们指定拖动手势的执行前提为轻扫手势失败就可以了,这样一来当我们手指轻轻滑动时系统会优先考虑轻扫手势,如果最后发现该操作不是轻扫,那么就会执行拖动。只要将下面的代码添加到添加手势之后就能解决这个问题了(注意为了更加清晰的区分拖动和轻扫[模拟器中拖动稍微快一点就识别成了轻扫],这里将长按手势的前提设置为拖动失败,避免演示拖动时长按手势会被识别):
//解决在图片上滑动时拖动手势和轻扫手势的冲突 [panGesture requireGestureRecognizerToFail:swipeGestureToRight]; [panGesture requireGestureRecognizerToFail:swipeGestureToLeft]; //解决拖动和长按手势之间的冲突 [longPressGesture requireGestureRecognizerToFail:panGesture];
运行效果:
两个不同控件的手势同时执行
我们知道在iOS的触摸事件中,事件触发是根据响应者链进行的,上层触摸事件执行后就不再向下传播。默认情况下手势也是类似的,先识别的手势会阻断手势识别操作继续传播。那么如何让两个有层次关系并且都添加了手势的控件都能正确识别手势呢?答案就是利用代理的-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer
*)otherGestureRecognizer方法。这个代理方法默认返回NO,会阻断继续向下识别手势,如果返回YES则可以继续向下传播识别。
下面的代码控制演示了当在图片上长按时同时可以识别控制器视图的长按手势(注意其中我们还控制了只有在UIImageView中操作的手势才能向下传递,如果不控制则所有控件都可以向下传递)
// // KCGestureViewController.m // TouchEventAndGesture // // Created by Kenshin Cui on 14-3-16. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCPhotoViewController.h" #define kImageCount 3 @interface KCPhotoViewController ()<UIGestureRecognizerDelegate>{ UIImageView *_imageView;//图片展示控件 int _currentIndex;//当前图片索引 } @end @implementation KCPhotoViewController - (void)viewDidLoad { [super viewDidLoad]; [self initLayout]; [self addGesture]; } #pragma mark 布局 -(void)initLayout{ /*添加图片展示控件*/ CGSize screenSize=[UIScreen mainScreen].applicationFrame.size; CGFloat topPadding=20; CGFloat y=22+44+topPadding,height=screenSize.height-y-topPadding; CGRect imageFrame=CGRectMake(0, y, screenSize.width, height); _imageView=[[UIImageView alloc]initWithFrame:imageFrame]; _imageView.contentMode=UIViewContentModeScaleToFill;//设置内容模式为缩放填充 _imageView.userInteractionEnabled=YES;//这里必须设置位YES,否则无法接收手势操作 //_imageView.multipleTouchEnabled=YES;//支持多点触摸,默认就是YES [self.view addSubview:_imageView]; //添加默认图片 UIImage *image=[UIImage imageNamed:@"0.jpg"]; _imageView.image=image; [self showPhotoName]; } #pragma mark 添加手势 -(void)addGesture{ /*添加点按手势*/ //创建手势对象 UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)]; //设置手势属性 tapGesture.numberOfTapsRequired=1;//设置点按次数,默认为1,注意在iOS中很少用双击操作 tapGesture.numberOfTouchesRequired=1;//点按的手指数 //添加手势到对象(注意,这里添加到了控制器视图中,而不是图片上,否则点击空白无法隐藏导航栏) [self.view addGestureRecognizer:tapGesture]; /*添加长按手势*/ UILongPressGestureRecognizer *longPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressImage:)]; longPressGesture.minimumPressDuration=0.5;//设置长按时间,默认0.5秒,一般这个值不要修改 //注意由于我们要做长按提示删除操作,因此这个手势不再添加到控制器视图上而是添加到了图片上 [_imageView addGestureRecognizer:longPressGesture]; /*添加捏合手势*/ UIPinchGestureRecognizer *pinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchImage:)]; [self.view addGestureRecognizer:pinchGesture]; /*添加旋转手势*/ UIRotationGestureRecognizer *rotationGesture=[[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotateImage:)]; [self.view addGestureRecognizer:rotationGesture]; /*添加拖动手势*/ UIPanGestureRecognizer *panGesture=[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panImage:)]; [_imageView addGestureRecognizer:panGesture]; /*添加轻扫手势*/ //注意一个轻扫手势只能控制一个方向,默认向右,通过direction进行方向控制 UISwipeGestureRecognizer *swipeGestureToRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; //swipeGestureToRight.direction=UISwipeGestureRecognizerDirectionRight;//默认位向右轻扫 [self.view addGestureRecognizer:swipeGestureToRight]; UISwipeGestureRecognizer *swipeGestureToLeft=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; swipeGestureToLeft.direction=UISwipeGestureRecognizerDirectionLeft; [self.view addGestureRecognizer:swipeGestureToLeft]; //解决在图片上滑动时拖动手势和轻扫手势的冲突 [panGesture requireGestureRecognizerToFail:swipeGestureToRight]; [panGesture requireGestureRecognizerToFail:swipeGestureToLeft]; //解决拖动和长按手势之间的冲突 [longPressGesture requireGestureRecognizerToFail:panGesture]; /*演示不同视图的手势同时执行 *在上面_imageView已经添加了长按手势,这里给视图控制器的视图也加上长按手势让两者都执行 * */ self.view.tag=100; _imageView.tag=200; UILongPressGestureRecognizer *viewLongPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressView:)]; viewLongPressGesture.delegate=self; [self.view addGestureRecognizer:viewLongPressGesture]; } #pragma mark 显示图片名称 -(void)showPhotoName{ NSString *title=[NSString stringWithFormat:@"%i.jpg",_currentIndex]; [self setTitle:title]; } #pragma mark 下一张图片 -(void)nextImage{ int index=(_currentIndex+kImageCount+1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName]; } #pragma mark 上一张图片 -(void)lastImage{ int index=(_currentIndex+kImageCount-1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName]; } #pragma mark - 手势操作 #pragma mark 点按隐藏或显示导航栏 -(void)tapImage:(UITapGestureRecognizer *)gesture{ //NSLog(@"tap:%i",gesture.state); BOOL hidden=!self.navigationController.navigationBarHidden; [self.navigationController setNavigationBarHidden:hidden animated:YES]; } #pragma mark 长按提示是否删除 -(void)longPressImage:(UILongPressGestureRecognizer *)gesture{ //NSLog(@"longpress:%i",gesture.state); //注意其实在手势里面有一个view属性可以获取点按的视图 //UIImageView *imageView=(UIImageView *)gesture.view; //由于连续手势此方法会调用多次,所以需求判断其手势状态 if (gesture.state==UIGestureRecognizerStateBegan) { UIActionSheet *actionSheet=[[UIActionSheet alloc]initWithTitle:@"System Info" delegate:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete the photo" otherButtonTitles:nil]; [actionSheet showInView:self.view]; } } #pragma mark 捏合时缩放图片 -(void)pinchImage:(UIPinchGestureRecognizer *)gesture{ //NSLog(@"pinch:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //捏合手势中scale属性记录的缩放比例 _imageView.transform=CGAffineTransformMakeScale(gesture.scale, gesture.scale); }else if(gesture.state==UIGestureRecognizerStateEnded){//结束后恢复 [UIView animateWithDuration:.5 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消一切形变 }]; } } #pragma mark 旋转图片 -(void)rotateImage:(UIRotationGestureRecognizer *)gesture{ //NSLog(@"rotate:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //旋转手势中rotation属性记录了旋转弧度 _imageView.transform=CGAffineTransformMakeRotation(gesture.rotation); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.8 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消形变 }]; } } #pragma mark 拖动图片 -(void)panImage:(UIPanGestureRecognizer *)gesture{ if (gesture.state==UIGestureRecognizerStateChanged) { CGPoint translation=[gesture translationInView:self.view];//利用拖动手势的translationInView:方法取得在相对指定视图(控制器根视图)的移动 _imageView.transform=CGAffineTransformMakeTranslation(translation.x, translation.y); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.5 animations:^{ _imageView.transform=CGAffineTransformIdentity; }]; } } #pragma mark 轻扫则查看下一张或上一张 //注意虽然轻扫手势是连续手势,但是只有在识别结束才会触发,不用判断状态 -(void)swipeImage:(UISwipeGestureRecognizer *)gesture{ // NSLog(@"swip:%i",gesture.state); // if (gesture.state==UIGestureRecognizerStateEnded) { //direction记录的轻扫的方向 if (gesture.direction==UISwipeGestureRecognizerDirectionRight) {//向右 [self nextImage]; // NSLog(@"right"); }else if(gesture.direction==UISwipeGestureRecognizerDirectionLeft){//向左 // NSLog(@"left"); [self lastImage]; } // } } #pragma mark 控制器视图的长按手势 -(void)longPressView:(UILongPressGestureRecognizer *)gesture{ NSLog(@"view long press!"); } #pragma mark 手势代理方法 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ //NSLog(@"%i,%i",gestureRecognizer.view.tag,otherGestureRecognizer.view.tag); //注意,这里控制只有在UIImageView中才能向下传播,其他情况不允许 if ([otherGestureRecognizer.view isKindOfClass:[UIImageView class]]) { return YES; } return NO; } #pragma mark - 触摸事件 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"touch begin..."); } -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"touch end."); } @end