UIGestureRecognizer and UIMenuController

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é)巫玻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市祠汇,隨后出現(xiàn)的幾起案子仍秤,更是在濱河造成了極大的恐慌,老刑警劉巖座哩,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徒扶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡根穷,警方通過(guò)查閱死者的電腦和手機(jī)姜骡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)导坟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人圈澈,你說(shuō)我怎么就攤上這事惫周。” “怎么了康栈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵递递,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我啥么,道長(zhǎng)登舞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任悬荣,我火速辦了婚禮菠秒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘氯迂。我一直安慰自己践叠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布嚼蚀。 她就那樣靜靜地躺著禁灼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轿曙。 梳的紋絲不亂的頭發(fā)上弄捕,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音导帝,去河邊找鬼察藐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛舟扎,可吹牛的內(nèi)容都是我干的分飞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼睹限,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼譬猫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起羡疗,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤染服,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后叨恨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體柳刮,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秉颗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痢毒。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蚕甥,靈堂內(nèi)的尸體忽然破棺而出哪替,到底是詐尸還是另有隱情,我是刑警寧澤菇怀,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布凭舶,位于F島的核電站,受9級(jí)特大地震影響爱沟,放射性物質(zhì)發(fā)生泄漏帅霜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一呼伸、第九天 我趴在偏房一處隱蔽的房頂上張望义屏。 院中可真熱鬧,春花似錦蜂大、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至踢星,卻和暖如春澳叉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沐悦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工成洗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藏否。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓瓶殃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親副签。 傳聞我的和親對(duì)象是個(gè)殘疾皇子遥椿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容