UIGestureRecognizer and UIMenuController

UIGestureRecognizer and UIMenuController

UIGestureRecognizer有許多子類,響應(yīng)不同的手勢。

為UIGestureRecognizer實例指定target-action,并將UIGestureRecognizer實例綁定到view上,當(dāng)UIGestureRecognizer實例識別view上的某種手勢后,他會發(fā)送指定的action消息到target。

所有的UIGestureRecognizer action 消息都是以下的形式:

- (void)action:(UIGestureRecognizer *)gestureRecognizer;

Gesture recognizer攔截view上的touch事件,因此一個有g(shù)esture recognizer的view可能不會收到UIResponder的消息,如touchesBegan:withEvent:

UITapGestureRecognizer

UITapGestureRecognizer是UIGestureRecognizer的子類。
在BKDrawView.m,在初始化方法中為其綁定監(jiān)聽雙擊的gesture recognizer,gesture recognizer的target是view本身,action為doubleTap,所以當(dāng)雙擊事件被監(jiān)聽到,會執(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)聽雙擊的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,并且是這個gesture recognizer發(fā)送了doubleTap消息到target.

此時雙擊view,會顯示如下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檢查觸摸事件,來判斷是否其監(jiān)聽的手勢發(fā)生了。在gesture recognizer識別手勢之前,這些UIResponder 消息仍會正常發(fā)送給view。

當(dāng)touch事件發(fā)生,而gesture recognizer還不能識別為其監(jiān)聽的手勢時,則touchesBegan:withEvent: 會被發(fā)送給view,而當(dāng)gesture recognizer識別了手勢,UIResponder消息不再發(fā)送給view,為了告訴view touch事件被接管,會發(fā)送touchesCancelled:withEvent:給view。

為阻止這種情況發(fā)生,可以讓gesture recognizer延遲發(fā)送touchesBegan:withEvent: 給view,即當(dāng)touch不再可能被識別成特定的gesture了,再發(fā)送touchesBegan:withEvent: 消息給view。

// 當(dāng)touch不再可能被識別為double tap gesture了,再發(fā)送touches began
doubleTapRecognizer.delaysTouchesBegan = YES;

Multiple Gesture Recognizer

再為BKDrawView添加監(jiān)聽單擊的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)聽雙擊的gesture recognizer
        UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
        doubleTapRecognizer.numberOfTapsRequired = 2;
        // 當(dāng)touch不再可能被識別為double tap gesture了,再發(fā)送touches began
        doubleTapRecognizer.delaysTouchesBegan = YES;
        // 將gesture recognizer關(guān)聯(lián)到view上
        [self addGestureRecognizer:doubleTapRecognizer];
        
        // 添加監(jiān)聽單擊的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上有兩個gesture recognizer,雙擊view,既會觸發(fā)監(jiān)聽單擊的gesture recognizer,也會觸發(fā)監(jiān)聽雙擊的gesture recognizer,此時需要在這些gesture recognizers之間添加dependency,就像在說”你等下,這個gesture可能是我的“。

// 單擊gesture recognizer要等待雙擊gesture recognizer失敗再觸發(fā)
[tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];

UIMenuController

UIMenuController 包含一組UIMenuItem對象,每個menu item有一個title和action(action消息被發(fā)送給window的first responder)。

一個application只有一個UIMenuController對象,當(dāng)要顯示menu controller時,要為其設(shè)置menu items,指定一個距形去顯示,并設(shè)置為可見。

- (void)tap:(UIGestureRecognizer *)gr{
    NSLog(@"Recognized tap");
    // 找到當(dāng)前手勢在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è)置為可見
        [menu setMenuVisible:YES animated:YES];
    }else{
        // 隱藏menu controller
        [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
    }
    
    [self setNeedsDisplay];
}

上面代碼中,首先將view自身設(shè)置為window的first responder,一個自定義view要成為first responder,必須重寫canBecomeFirstResponder方法,并返回YES.

// 重寫canBecomeFirstResponder方法,并返回YES,使得當(dāng)前VIEW可以成為first responder
- (BOOL)canBecomeFirstResponder{
    return YES;
}

現(xiàn)在運(yùn)行程序,你會發(fā)現(xiàn)menu并沒有出現(xiàn),因為first responder沒有menu item對應(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來改變持續(xù)時間。

// 添加監(jiān)聽長按的gesture recognizer
UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[self addGestureRecognizer:pressRecognizer];

不像tap這種簡單的gesture recognizer,long press gesture recognizer有三種state:UIGestureRecognizerStatePossible, UIGestureRecognizerStateBegan, UIGestureRecognizerStateEnded。
當(dāng)gesture recognizer狀態(tài)變成非possible時,就會發(fā)送action消息到target,所以在press gesture的開始和結(jié)束狀態(tài),target都會收到action消息。

下面當(dāng)長按屏幕時,選中最近的一條線,當(dā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)用戶長按線條,然后移動線條,這個動作就叫panning。

通常gesture recognizer不分享其捕獲的touch,一旦識別為了gesture,這些touch不會再被其他gesture recognizer處理。然而pan gesture發(fā)生在long press gesture中,需要這兩種gesture recognizer能夠同時識別gesture。

為了實現(xiàn)這種共享touch,需要實現(xiàn)UIGestureRecognizerDelegate protocol的
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法,當(dāng)此方法返回YES,gesture recognizer之間可以分享touches。

pan gesture recognizer支持changed state,當(dāng)手指開始移動,pan recognizer進(jìn)入began state并發(fā)送action消息到target,當(dāng)手指繼續(xù)移動,pan gesture recognizer進(jìn)入changed state并發(fā)送action消息到target。最后,當(dāng)手指離開屏幕,pan gesture recognizer進(jìn)入ended state并發(fā)送最后一次action消息到target。

@interface BKDrawView () <UIGestureRecognizerDelegate>

//@property (nonatomic, strong) BKLine *currentLine;
// 保存當(dāng)前的多個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)聽雙擊的gesture recognizer
        UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
        doubleTapRecognizer.numberOfTapsRequired = 2;
        // 當(dāng)touch不再可能被識別為double tap gesture了,再發(fā)送touches began
        doubleTapRecognizer.delaysTouchesBegan = YES;
        // 將gesture recognizer關(guān)聯(lián)到view上
        [self addGestureRecognizer:doubleTapRecognizer];
        
        // 添加監(jiān)聽單擊的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)聽長按的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)移動了多遠(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開始計算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移動了多遠(yuǎn),返回的坐標(biāo)CGPoint是在X,Y軸上移動距離的值。
每個UIGestureRecognizer都有cancelsTouchesInView屬性,默認(rèn)值是YES,意思是gesture recognizer會吃掉其識別的touch event,從而不會觸發(fā)UIResponder的方法,如touchesBegan:withEvent:,將其設(shè)置為NO,從而使得touchesMoved:withEvent:可以被執(zhí)行,因為gesture recognizer是攔截器,他控制了是否執(zhí)行UIResponder的方法。

UIResponderStandardEditActions

UIResponderStandardEditActions協(xié)議聲明了UIMenuController中的action方法,如果view實現(xiàn)了這些方法,當(dāng)UIMenuController顯示時,就會顯示對應(yīng)的menu item。
判斷view是否實現(xiàn)了某方法,由canPerformAction:withSender:方法來判斷,menu controller 會發(fā)送該消息給view,默認(rèn)是當(dāng)view實現(xiàn)了某方法,就返回YES,否則返回NO,我們可以重寫該方法。

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
    //return [super canPerformAction:action withSender:sender];
    return YES;
}

除了上面所講的gesture recognizer,還有三個內(nèi)置的gesture recognizer: UIPinchGestureRecognizer, UISwipeGestureRecognizer, UIRotationGestureRecognizer。


本文是對《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十三章的總結(jié)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容