1、UIGestureRecognizer介紹
手勢識別在iOS上非常重要,手勢操作移動設(shè)備的重要特征,極大的增加了移動設(shè)備使用便捷性。
iOS系統(tǒng)在3.2以后,為方便開發(fā)這使用一些常用的手勢,提供了UIGestureRecognizer類。手勢識別UIGestureRecognizer類是個抽象類,下面的子類是具體的手勢,開發(fā)這可以直接使用這些手勢識別。
上面的手勢對應(yīng)的操作是:
Tap(點一下)
Pinch(二指往內(nèi)或往外撥動,平時經(jīng)常用到的縮放)
Rotation(旋轉(zhuǎn))
Swipe(滑動,快速移動)
Pan (拖移,慢速移動)
LongPress(長按)
UIGestureRecognizer的繼承關(guān)系如下:

使用手勢很簡單,分為兩步:
創(chuàng)建手勢實例。當(dāng)創(chuàng)建手勢時,指定一個回調(diào)方法,當(dāng)手勢開始,改變、或結(jié)束時,回調(diào)方法被調(diào)用。
添加到需要識別的View中。每個手勢只對應(yīng)一個View,當(dāng)屏幕觸摸在View的邊界內(nèi)時,如果手勢和預(yù)定的一樣,那就會回調(diào)方法。
ps:一個手勢只能對應(yīng)一個View,但是一個View可以有多個手勢。
建議在真機上運行這些手勢,模擬器操作不太方便,可能導(dǎo)致你認(rèn)為手勢失效。
[cpp]view plaincopy
UIImageView?*snakeImageView?=?[[UIImageView?alloc]?initWithImage:[UIImage?imageNamed:@"snake.png"]];
snakeImageView.frame?=?CGRectMake(50,?50,?100,?160);
UIPanGestureRecognizer?*panGestureRecognizer?=?[[UIPanGestureRecognizer?alloc]
initWithTarget:self
action:@selector(handlePan:)];
[snakeImageView?addGestureRecognizer:panGestureRecognizer];
[self.view?setBackgroundColor:[UIColor?whiteColor]];
[self.view?addSubview:snakeImageView];
新建一個ImageView,然后添加手勢
回調(diào)方法:
[cpp]view plaincopy
-?(void)?handlePan:(UIPanGestureRecognizer*)?recognizer
{
CGPoint?translation?=?[recognizer?translationInView:self.view];
recognizer.view.center?=?CGPointMake(recognizer.view.center.x?+?translation.x,
recognizer.view.center.y?+?translation.y);
[recognizer?setTranslation:CGPointZero?inView:self.view];
}
[cpp]view plaincopy
UIPinchGestureRecognizer?*pinchGestureRecognizer?=?[[UIPinchGestureRecognizer?alloc]
initWithTarget:self
action:@selector(handlePinch:)];[snakeImageView?addGestureRecognizer:pinchGestureRecognizer];
[cpp]view plaincopy
-?(void)?handlePinch:(UIPinchGestureRecognizer*)?recognizer
{
recognizer.view.transform?=?CGAffineTransformScale(recognizer.view.transform,?recognizer.scale,?recognizer.scale);
recognizer.scale?=?1;
}
[cpp]view plaincopy
UIRotationGestureRecognizer?*rotateRecognizer?=?[[UIRotationGestureRecognizer?alloc]
initWithTarget:self
action:@selector(handleRotate:)];
[snakeImageView?addGestureRecognizer:rotateRecognizer];
[cpp]view plaincopy
-?(void)?handleRotate:(UIRotationGestureRecognizer*)?recognizer
{
recognizer.view.transform?=?CGAffineTransformRotate(recognizer.view.transform,?recognizer.rotation);
recognizer.rotation?=?0;
}


添加了這幾個手勢后,運行看效果,程序中的imageView放了一個
/^\/^\
_|__| ?O|
\/ ? ? /~ ? ? \_/ \
\____|__________/ ?\
\_______ ? ? ?\
`\ ? ? \ ? ? ? ? ? ? ? ? \
| ? ? | ? ? ? ? ? ? ? ? ?\
/ ? ? ?/ ? ? ? ? ? ? ? ? ? ?\
/ ? ? / ? ? ? ? ? ? ? ? ? ? ? \\
/ ? ? ?/ ? ? ? ? ? ? ? ? ? ? ? ? \ \
/ ? ? / ? ? ? ? ? ? ? ? ? ? ? ? ? ?\ ?\
/ ? ? / ? ? ? ? ? ? _----_ ? ? ? ? ? ?\ ? \
/ ? ? / ? ? ? ? ? _-~ ? ? ?~-_ ? ? ? ? | ? |
( ? ? ?( ? ? ? ?_-~ ? ?_--_ ? ?~-_ ? ? _/ ? |
\ ? ? ?~-____-~ ? ?_-~ ? ?~-_ ? ?~-_-~ ? ?/
~-_ ? ? ? ? ? _-~ ? ? ? ? ?~-_ ? ? ? _-~
~--______-~ ? ? ? ? ? ? ? ?~-___-~
的圖片,在模擬器上拖動是沒問題的??s放和旋轉(zhuǎn)有點問題,估計是因為在模擬器上的模擬的兩個接觸點距離在imageView的邊界外了,所以操作無效果。
建議在真機上運行這個手勢。
在模擬器上縮放和選擇的操作技巧:
可以把imageView的frame值設(shè)置大一點,按住alt鍵,按下觸摸板(不按下不行),這樣就可以旋轉(zhuǎn)和縮放了。
記?。阂粋€手勢只能添加到一個View,兩個View當(dāng)然要有兩個手勢的實例了
[cpp]view plaincopy
-?(void)viewDidLoad
{
[super?viewDidLoad];
UIImageView?*snakeImageView?=?[[UIImageView?alloc]?initWithImage:[UIImage?imageNamed:@"snake.png"]];
UIImageView?*dragonImageView?=?[[UIImageView?alloc]?initWithImage:[UIImage?imageNamed:@"dragon.png"]];
snakeImageView.frame?=?CGRectMake(120,?120,?100,?160);
dragonImageView.frame?=?CGRectMake(50,?50,?100,?160);
[self.view?addSubview:snakeImageView];
[self.view?addSubview:dragonImageView];
for(UIView?*view?in?self.view.subviews)?{
UIPanGestureRecognizer?*panGestureRecognizer?=?[[UIPanGestureRecognizer?alloc]
initWithTarget:self
action:@selector(handlePan:)];
UIPinchGestureRecognizer?*pinchGestureRecognizer?=?[[UIPinchGestureRecognizer?alloc]
initWithTarget:self
action:@selector(handlePinch:)];
UIRotationGestureRecognizer?*rotateRecognizer?=?[[UIRotationGestureRecognizer?alloc]
initWithTarget:self
action:@selector(handleRotate:)];
[view?addGestureRecognizer:panGestureRecognizer];
[view?addGestureRecognizer:pinchGestureRecognizer];
[view?addGestureRecognizer:rotateRecognizer];
[view?setUserInteractionEnabled:YES];
}
[self.view?setBackgroundColor:[UIColor?whiteColor]];
}
多添加了一條龍的view,兩個view都能接收上面的三種手勢。運行效果如下:

7、拖動(pan手勢)速度(以較快的速度拖放后view有滑行的效果)
如何實現(xiàn)呢?
監(jiān)視手勢是否結(jié)束
監(jiān)視觸摸的速度
[cpp]view plaincopy
-?(void)?handlePan:(UIPanGestureRecognizer*)?recognizer
{
CGPoint?translation?=?[recognizer?translationInView:self.view];
recognizer.view.center?=?CGPointMake(recognizer.view.center.x?+?translation.x,
recognizer.view.center.y?+?translation.y);
[recognizer?setTranslation:CGPointZero?inView:self.view];
if(recognizer.state?==?UIGestureRecognizerStateEnded)?{
CGPoint?velocity?=?[recognizer?velocityInView:self.view];
CGFloat?magnitude?=?sqrtf((velocity.x?*?velocity.x)?+?(velocity.y?*?velocity.y));
CGFloat?slideMult?=?magnitude?/?200;
NSLog(@"magnitude:?%f,?slideMult:?%f",?magnitude,?slideMult);
floatslideFactor?=?0.1?*?slideMult;//?Increase?for?more?of?a?slide
CGPoint?finalPoint?=?CGPointMake(recognizer.view.center.x?+?(velocity.x?*?slideFactor),
recognizer.view.center.y?+?(velocity.y?*?slideFactor));
finalPoint.x?=?MIN(MAX(finalPoint.x,?0),?self.view.bounds.size.width);
finalPoint.y?=?MIN(MAX(finalPoint.y,?0),?self.view.bounds.size.height);
[UIView?animateWithDuration:slideFactor*2?delay:0?options:UIViewAnimationOptionCurveEaseOut?animations:^{
recognizer.view.center?=?finalPoint;
}?completion:nil];
}
代碼實現(xiàn)解析:
計算速度向量的長度(估計大部分都忘了)這些知識了。
如果速度向量小于200,那就會得到一個小于的小數(shù),那么滑行會很短
基于速度和速度因素計算一個終點
確保終點不會跑出父View的邊界
使用UIView動畫使view滑動到終點
運行后,快速拖動圖像view放開會看到view還會在原來的方向滑行一段路。
手勢之間是互斥的,如果你想同時觸發(fā)蛇和龍的view,那么需要實現(xiàn)協(xié)議
UIGestureRecognizerDelegate,
[cpp]view plaincopy
@interface?ViewController?:?UIViewController
@end
并在協(xié)議這個方法里返回YES。
[cpp]view plaincopy
-(BOOL)gestureRecognizer:(UIGestureRecognizer?*)gestureRecognizer?shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer?*)otherGestureRecognizer
{
returnYES;
}
把self作為代理設(shè)置給手勢:
[cpp]view plaincopy
panGestureRecognizer.delegate?=?self;
pinchGestureRecognizer.delegate?=?self;
rotateRecognizer.delegate?=?self;
這樣可以同時拖動或旋轉(zhuǎn)縮放兩個view了。
這里為了方便看到tap的效果,當(dāng)點擊一下屏幕時,播放一個聲音。
為了播放聲音,我們加入AVFoundation.framework這個框架。
[cpp]view plaincopy
-?(AVAudioPlayer?*)loadWav:(NSString?*)filename?{
NSURL?*?url?=?[[NSBundle?mainBundle]?URLForResource:filename?withExtension:@"wav"];
NSError?*?error;
AVAudioPlayer?*?player?=?[[AVAudioPlayer?alloc]?initWithContentsOfURL:url?error:&error];
if(!player)?{
NSLog(@"Error?loading?%@:?%@",?url,?error.localizedDescription);
}else{
[player?prepareToPlay];
}
returnplayer;
}
我會在最后例子代碼給出完整代碼,添加手勢的步驟和前面一樣的。
[cpp]view plaincopy
#import?
#import?
@interface?ViewController?:?UIViewController
@property?(strong)?AVAudioPlayer?*?chompPlayer;
@property?(strong)?AVAudioPlayer?*?hehePlayer;
@end
[cpp]view plaincopy
-?(void)handleTap:(UITapGestureRecognizer?*)recognizer?{
[self.chompPlayer?play];
}
運行,點一下某個圖,就會播放一個咬東西的聲音。
不過這個點擊播放聲音有點缺陷,就是在慢慢拖動的時候也會播放。這使得兩個手勢重合了。怎么解決呢?使用手勢的:requireGestureRecognizerToFail方法。
在viewDidLoad的循環(huán)里添加這段代碼:
[cpp]view plaincopy
[tapRecognizer?requireGestureRecognizerToFail:panGestureRecognizer];
意思就是,當(dāng)如果pan手勢失敗,就是沒發(fā)生拖動,才會出發(fā)tap手勢。這樣如果你有輕微的拖動,那就是pan手勢發(fā)生了。tap的聲音就不會發(fā)出來了。
自定義手勢繼承:UIGestureRecognizer,實現(xiàn)下面的方法:
[cpp]view plaincopy
–?touchesBegan:withEvent:
–?touchesMoved:withEvent:
–?touchesEnded:withEvent:
-?touchesCancelled:withEvent:
新建一個類,繼承UIGestureRecognizer,代碼如下:
.h文件
[cpp]view plaincopy
#import?
typedefenum{
DirectionUnknown?=?0,
DirectionLeft,
DirectionRight
}?Direction;
@interface?HappyGestureRecognizer?:?UIGestureRecognizer
@property?(assign)inttickleCount;
@property?(assign)?CGPoint?curTickleStart;
@property?(assign)?Direction?lastDirection;
@end
.m文件
[cpp]view plaincopy
#import?"HappyGestureRecognizer.h"
#import?
#define?REQUIRED_TICKLES????????2
#define?MOVE_AMT_PER_TICKLE?????25
@implementation?HappyGestureRecognizer
-?(void)touchesBegan:(NSSet?*)touches?withEvent:(UIEvent?*)event?{
UITouch?*?touch?=?[touches?anyObject];
self.curTickleStart?=?[touch?locationInView:self.view];
}
-?(void)touchesMoved:(NSSet?*)touches?withEvent:(UIEvent?*)event?{
//?Make?sure?we've?moved?a?minimum?amount?since?curTickleStart
UITouch?*?touch?=?[touches?anyObject];
CGPoint?ticklePoint?=?[touch?locationInView:self.view];
CGFloat?moveAmt?=?ticklePoint.x?-?self.curTickleStart.x;
Direction?curDirection;
if(moveAmt?<?0)?{
curDirection?=?DirectionLeft;
}else{
curDirection?=?DirectionRight;
}
if(ABS(moveAmt)?<?MOVE_AMT_PER_TICKLE)return;
//?確認(rèn)方向改變了
if(self.lastDirection?==?DirectionUnknown?||
(self.lastDirection?==?DirectionLeft?&&?curDirection?==?DirectionRight)?||
(self.lastDirection?==?DirectionRight?&&?curDirection?==?DirectionLeft))?{
//?撓癢次數(shù)
self.tickleCount++;
self.curTickleStart?=?ticklePoint;
self.lastDirection?=?curDirection;
//?一旦撓癢次數(shù)超過指定數(shù),設(shè)置手勢為結(jié)束狀態(tài)
//?這樣回調(diào)函數(shù)會被調(diào)用。
if(self.state?==?UIGestureRecognizerStatePossible?&&?self.tickleCount?>?REQUIRED_TICKLES)?{
[self?setState:UIGestureRecognizerStateEnded];
}
}
}
-?(void)reset?{
self.tickleCount?=?0;
self.curTickleStart?=?CGPointZero;
self.lastDirection?=?DirectionUnknown;
if(self.state?==?UIGestureRecognizerStatePossible)?{
[self?setState:UIGestureRecognizerStateFailed];
}
}
-?(void)touchesEnded:(NSSet?*)touches?withEvent:(UIEvent?*)event
{
[self?reset];
}
-?(void)touchesCancelled:(NSSet?*)touches?withEvent:(UIEvent?*)event
{
[self?reset];
}
@end
調(diào)用自定義手勢和上面一樣,回到這樣寫:
[cpp]view plaincopy
-?(void)handleHappy:(HappyGestureRecognizer?*)recognizer{
[self.hehePlayer?play];
}
手勢成功后播放呵呵笑的聲音。
在真機上運行,按住某個view,快速左右拖動,就會發(fā)出笑的聲音了。
代碼解析:
先獲取起始坐標(biāo):curTickleStart
通過和ticklePoint的x值對比,得出當(dāng)前的放下是向左還是向右。再算出移動的x的值是否比MOVE_AMT_PER_TICKLE距離大,如果太則返回。
再判斷是否有三次是不同方向的動作,如果是則手勢結(jié)束,回調(diào)。