為了幫助大家理解,下面以一個圖片查看程序演示一下上面幾種手勢,在這個程序中我們完成以下功能:
如果點按圖片會在導航欄顯示圖片名稱;
如果長按圖片會顯示刪除按鈕,提示用戶是否刪除;
如果捏合會放大、縮小圖片;
如果輕掃會切換到下一張或上一張圖片;
如果旋轉會旋轉圖片;
如果拖動會移動圖片;
具體布局草圖如下:
為了顯示導航條,我們首先將主視圖控制器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 (){ 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