UIGestureRecognizer and UIMenuController
UIGestureRecognizer有許多子類(lèi),響應(yīng)不同的手勢(shì)魁蒜。
為UIGestureRecognizer實(shí)例指定target-action,并將UIGestureRecognizer實(shí)例綁定到view上,當(dāng)UIGestureRecognizer實(shí)例識(shí)別view上的某種手勢(shì)后,他會(huì)發(fā)送指定的action消息到target次氨。
所有的UIGestureRecognizer action 消息都是以下的形式:
- (void)action:(UIGestureRecognizer *)gestureRecognizer;
Gesture recognizer攔截view上的touch事件,因此一個(gè)有g(shù)esture recognizer的view可能不會(huì)收到UIResponder的消息摘投,如touchesBegan:withEvent:
UITapGestureRecognizer
UITapGestureRecognizer是UIGestureRecognizer的子類(lèi)煮寡。
在BKDrawView.m屉佳,在初始化方法中為其綁定監(jiān)聽(tīng)雙擊的gesture recognizer,gesture recognizer的target是view本身洲押,action為doubleTap,所以當(dāng)雙擊事件被監(jiān)聽(tīng)到圆凰,會(huì)執(zhí)行target的doubleTap方法杈帐。
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.linesInProgress = [[NSMutableDictionary alloc] init];
self.finishedLines = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor grayColor];
// 啟用multiple touches
self.multipleTouchEnabled = YES;
// 創(chuàng)建監(jiān)聽(tīng)雙擊的gesture recognizer
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap)];
doubleTapRecognizer.numberOfTapsRequired = 2;
// 將gesture recognizer關(guān)聯(lián)到view上
[self addGestureRecognizer:doubleTapRecognizer];
}
return self;
}
- (void)doubleTap:(UIGestureRecognizer *)gr{
NSLog(@"Recognized Double Tap");
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
}
doubleTap方法的參數(shù)是gesture recognizer,并且是這個(gè)gesture recognizer發(fā)送了doubleTap消息到target.
此時(shí)雙擊view专钉,會(huì)顯示如下log:
2015-08-06 00:20:46.639 TouchTracker[3301:70b] touchesBegan:withEvent:
2015-08-06 00:20:46.767 TouchTracker[3301:70b] Recognized Double Tap
2015-08-06 00:20:46.768 TouchTracker[3301:70b] touchesCancelled:withEvent:
Gesture recognizer檢查觸摸事件挑童,來(lái)判斷是否其監(jiān)聽(tīng)的手勢(shì)發(fā)生了。在gesture recognizer識(shí)別手勢(shì)之前跃须,這些UIResponder 消息仍會(huì)正常發(fā)送給view站叼。
當(dāng)touch事件發(fā)生,而gesture recognizer還不能識(shí)別為其監(jiān)聽(tīng)的手勢(shì)時(shí)菇民,則touchesBegan:withEvent: 會(huì)被發(fā)送給view尽楔,而當(dāng)gesture recognizer識(shí)別了手勢(shì),UIResponder消息不再發(fā)送給view第练,為了告訴view touch事件被接管阔馋,會(huì)發(fā)送touchesCancelled:withEvent:給view。
為阻止這種情況發(fā)生娇掏,可以讓gesture recognizer延遲發(fā)送touchesBegan:withEvent: 給view呕寝,即當(dāng)touch不再可能被識(shí)別成特定的gesture了,再發(fā)送touchesBegan:withEvent: 消息給view婴梧。
// 當(dāng)touch不再可能被識(shí)別為double tap gesture了下梢,再發(fā)送touches began
doubleTapRecognizer.delaysTouchesBegan = YES;
Multiple Gesture Recognizer
再為BKDrawView添加監(jiān)聽(tīng)單擊的gesture recognizer:
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.linesInProgress = [[NSMutableDictionary alloc] init];
self.finishedLines = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor grayColor];
// 啟用multiple touches
self.multipleTouchEnabled = YES;
// 創(chuàng)建監(jiān)聽(tīng)雙擊的gesture recognizer
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
// 當(dāng)touch不再可能被識(shí)別為double tap gesture了,再發(fā)送touches began
doubleTapRecognizer.delaysTouchesBegan = YES;
// 將gesture recognizer關(guān)聯(lián)到view上
[self addGestureRecognizer:doubleTapRecognizer];
// 添加監(jiān)聽(tīng)單擊的gesture recognizer
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
[self addGestureRecognizer:tapRecognizer];
}
return self;
}
- (void)doubleTap:(UIGestureRecognizer *)gr{
NSLog(@"Recognized Double Tap");
[self.linesInProgress removeAllObjects];
[self.finishedLines removeAllObjects];
[self setNeedsDisplay];
}
- (void)tap:(UIGestureRecognizer *)gr{
NSLog(@"Recognized tap");
}
現(xiàn)在view上有兩個(gè)gesture recognizer塞蹭,雙擊view孽江,既會(huì)觸發(fā)監(jiān)聽(tīng)單擊的gesture recognizer,也會(huì)觸發(fā)監(jiān)聽(tīng)雙擊的gesture recognizer浮还,此時(shí)需要在這些gesture recognizers之間添加dependency竟坛,就像在說(shuō)”你等下,這個(gè)gesture可能是我的“钧舌。
// 單擊gesture recognizer要等待雙擊gesture recognizer失敗再觸發(fā)
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
UIMenuController
UIMenuController 包含一組UIMenuItem對(duì)象担汤,每個(gè)menu item有一個(gè)title和action(action消息被發(fā)送給window的first responder)。
一個(gè)application只有一個(gè)UIMenuController對(duì)象洼冻,當(dāng)要顯示menu controller時(shí)崭歧,要為其設(shè)置menu items,指定一個(gè)距形去顯示撞牢,并設(shè)置為可見(jiàn)率碾。
- (void)tap:(UIGestureRecognizer *)gr{
NSLog(@"Recognized tap");
// 找到當(dāng)前手勢(shì)在view中的坐標(biāo)
CGPoint point = [gr locationInView:self];
self.selectedLine = [self lineAtPoint:point];
if (self.selectedLine) {
// 將view本身設(shè)置為window的first responder
[self becomeFirstResponder];
// 獲得menu controller單例
UIMenuController *menu = [UIMenuController sharedMenuController];
// 創(chuàng)建menu item叔营,并指定title和action
UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
// 為menu controller 設(shè)置menu items
menu.menuItems = @[deleteItem];
// 為menu controller指定距形
[menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
// 將menu controlelr設(shè)置為可見(jiàn)
[menu setMenuVisible:YES animated:YES];
}else{
// 隱藏menu controller
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
}
[self setNeedsDisplay];
}
上面代碼中,首先將view自身設(shè)置為window的first responder所宰,一個(gè)自定義view要成為first responder绒尊,必須重寫(xiě)canBecomeFirstResponder方法,并返回YES.
// 重寫(xiě)canBecomeFirstResponder方法仔粥,并返回YES婴谱,使得當(dāng)前VIEW可以成為first responder
- (BOOL)canBecomeFirstResponder{
return YES;
}
現(xiàn)在運(yùn)行程序,你會(huì)發(fā)現(xiàn)menu并沒(méi)有出現(xiàn)躯泰,因?yàn)閒irst responder沒(méi)有menu item對(duì)應(yīng)的action方法谭羔,添加deleteLine方法:
- (void)deleteLine:(id)sender{
[self.finishedLines removeObject:self.selectedLine];
// Redraw everything
[self setNeedsDisplay];
}
UILongPressGestureRecognizer
為BKDrawView添加long press gesture recognizer,默認(rèn)touch持續(xù)0.5秒即為long press麦向,可以修改minimumPressDuration來(lái)改變持續(xù)時(shí)間瘟裸。
// 添加監(jiān)聽(tīng)長(zhǎng)按的gesture recognizer
UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[self addGestureRecognizer:pressRecognizer];
不像tap這種簡(jiǎn)單的gesture recognizer,long press gesture recognizer有三種state:UIGestureRecognizerStatePossible, UIGestureRecognizerStateBegan, UIGestureRecognizerStateEnded诵竭。
當(dāng)gesture recognizer狀態(tài)變成非possible時(shí)话告,就會(huì)發(fā)送action消息到target,所以在press gesture的開(kāi)始和結(jié)束狀態(tài)秀撇,target都會(huì)收到action消息超棺。
下面當(dāng)長(zhǎng)按屏幕時(shí),選中最近的一條線呵燕,當(dāng)長(zhǎng)按結(jié)束棠绘,釋放選中的線:
- (void)longPress:(UIGestureRecognizer *)gr{
if(gr.state == UIGestureRecognizerStateBegan){
CGPoint point = [gr locationInView:self];
self.selectedLine = [self lineAtPoint:point];
if (self.selectedLine) {
[self.linesInProgress removeAllObjects];
}
}else if(gr.state == UIGestureRecognizerStateEnded){
self.selectedLine = nil;
}
[self setNeedsDisplay];
}
UIPanGestureRecognizer
當(dāng)用戶(hù)長(zhǎng)按線條,然后移動(dòng)線條再扭,這個(gè)動(dòng)作就叫panning氧苍。
通常gesture recognizer不分享其捕獲的touch,一旦識(shí)別為了gesture泛范,這些touch不會(huì)再被其他gesture recognizer處理让虐。然而pan gesture發(fā)生在long press gesture中,需要這兩種gesture recognizer能夠同時(shí)識(shí)別gesture罢荡。
為了實(shí)現(xiàn)這種共享touch赡突,需要實(shí)現(xiàn)UIGestureRecognizerDelegate protocol的
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法,當(dāng)此方法返回YES区赵,gesture recognizer之間可以分享touches惭缰。
pan gesture recognizer支持changed state,當(dāng)手指開(kāi)始移動(dòng)笼才,pan recognizer進(jìn)入began state并發(fā)送action消息到target漱受,當(dāng)手指繼續(xù)移動(dòng),pan gesture recognizer進(jìn)入changed state并發(fā)送action消息到target骡送。最后昂羡,當(dāng)手指離開(kāi)屏幕絮记,pan gesture recognizer進(jìn)入ended state并發(fā)送最后一次action消息到target。
@interface BKDrawView () <UIGestureRecognizerDelegate>
//@property (nonatomic, strong) BKLine *currentLine;
// 保存當(dāng)前的多個(gè)line
@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
@property (nonatomic, strong) NSMutableArray *finishedLines;
@property (nonatomic, weak) BKLine *selectedLine;
@property (nonatomic, strong) UIPanGestureRecognizer *moveRecognizer;
@end
@implementation BKDrawView
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.linesInProgress = [[NSMutableDictionary alloc] init];
self.finishedLines = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor grayColor];
// 啟用multiple touches
self.multipleTouchEnabled = YES;
// 創(chuàng)建監(jiān)聽(tīng)雙擊的gesture recognizer
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
// 當(dāng)touch不再可能被識(shí)別為double tap gesture了虐先,再發(fā)送touches began
doubleTapRecognizer.delaysTouchesBegan = YES;
// 將gesture recognizer關(guān)聯(lián)到view上
[self addGestureRecognizer:doubleTapRecognizer];
// 添加監(jiān)聽(tīng)單擊的gesture recognizer
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapRecognizer.delaysTouchesBegan = YES;
// 單擊gesture recognizer要等待雙擊gesture recognizer失敗再觸發(fā)
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
[self addGestureRecognizer:tapRecognizer];
// 添加監(jiān)聽(tīng)長(zhǎng)按的geture recognizer
UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[self addGestureRecognizer:pressRecognizer];
// pan gesture recognizer
self.moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];
self.moveRecognizer.delegate = self;
self.moveRecognizer.cancelsTouchesInView = NO;
[self addGestureRecognizer:self.moveRecognizer];
}
return self;
}
- (void)moveLine:(UIPanGestureRecognizer *)pgr{
if (!self.selectedLine) {
return;
}
// When the pan recognizer changes its positon
if (pgr.state == UIGestureRecognizerStateChanged) {
// translationInView方法返回怨愤,pan gesture已經(jīng)移動(dòng)了多遠(yuǎn)
CGPoint translation = [pgr translationInView:self];
CGPoint begin = self.selectedLine.begin;
CGPoint end = self.selectedLine.end;
begin.x += translation.x;
begin.y += translation.y;
end.x += translation.x;
end.y += translation.y;
self.selectedLine.begin = begin;
self.selectedLine.end = end;
[self setNeedsDisplay];
// 重置translationInVew為零,使得從上一次change事件后從0開(kāi)始計(jì)算translation
[pgr setTranslation:CGPointZero inView:self];
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (gestureRecognizer == self.moveRecognizer) {
return YES;
}
return NO;
}
UIPanGestureRecognizer的translationInView:方法返回pan gesture移動(dòng)了多遠(yuǎn)蛹批,返回的坐標(biāo)CGPoint是在X,Y軸上移動(dòng)距離的值憔四。
每個(gè)UIGestureRecognizer都有cancelsTouchesInView屬性,默認(rèn)值是YES般眉,意思是gesture recognizer會(huì)吃掉其識(shí)別的touch event,從而不會(huì)觸發(fā)UIResponder的方法潜支,如touchesBegan:withEvent:甸赃,將其設(shè)置為NO,從而使得touchesMoved:withEvent:可以被執(zhí)行冗酿,因?yàn)間esture recognizer是攔截器埠对,他控制了是否執(zhí)行UIResponder的方法。
UIResponderStandardEditActions
UIResponderStandardEditActions協(xié)議聲明了UIMenuController中的action方法裁替,如果view實(shí)現(xiàn)了這些方法项玛,當(dāng)UIMenuController顯示時(shí),就會(huì)顯示對(duì)應(yīng)的menu item弱判。
判斷view是否實(shí)現(xiàn)了某方法襟沮,由canPerformAction:withSender:方法來(lái)判斷,menu controller 會(huì)發(fā)送該消息給view昌腰,默認(rèn)是當(dāng)view實(shí)現(xiàn)了某方法开伏,就返回YES,否則返回NO遭商,我們可以重寫(xiě)該方法固灵。
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
//return [super canPerformAction:action withSender:sender];
return YES;
}
除了上面所講的gesture recognizer,還有三個(gè)內(nèi)置的gesture recognizer: UIPinchGestureRecognizer, UISwipeGestureRecognizer, UIRotationGestureRecognizer劫流。
本文是對(duì)《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十三章的總結(jié)巫玻。