1. 程式人生 > >iOS開發進階-UITapGestureRecognizer使用技巧

iOS開發進階-UITapGestureRecognizer使用技巧

手勢互動是iOS開發中用的比較多的一個類,用途無處不在,這裡面也衍生了很多的需求和用法,UIGestureRecognizer很強大,它的子類包括很多,不過想要更完美的使用它,就需要了解它的底層原理和和一些特殊情況下的處理辦法,本文主要介紹UITapGestureRecognizer的一些技巧性的方法和策略。

新新增的屬性

UITapGestureRecognizer繼承於UIGestureRecognizer,可以看到其中多添加了兩個屬性。

@property (nonatomic) NSUInteger  numberOfTapsRequired;       // Default
is 1. The number of taps required to match @property (nonatomic) NSUInteger numberOfTouchesRequired __TVOS_PROHIBITED; // Default is 1. The number of fingers required to match

numberOfTapsRequired 是需要點選的次數, numberOfTouchesRequired 是需要點選的手指數。使用起來也很容易,這裡不過多的介紹。

繼承於UIGestureRecognizer使用較多的API

在日常開發中,我們使用最多主要有以下幾個API。

新增選擇子

- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action NS_DESIGNATED_INITIALIZER; // designated initializer
- (void)addTarget:(id)target action:(SEL)action;    // add a target/action pair. you can call this multiple times to specify multiple target/actions
- (void)removeTarget:
(nullable id)target action:(nullable SEL)action; // remove the specified target/action pair. passing nil for target matches all targets, and the same for actions

給Tap新增選擇子可以直接在UIGestureRecognizer初始化的時候新增,也可以在初始化完畢之後使用addTarget方法新增。使用removeTarget 可以移除對應的選擇子。

獲取點選點座標

- (CGPoint)locationInView:(nullable UIView*)view;                                // a generic single-point location for the gesture. usually the centroid of the touches involved

這個方法用的很多,有時候根據不同的需求可以獲取點選點的座標進行相應的判斷和處理,使用起來也很容易。

UIGestureRecognizerDelegate相關方法

如果需要實現一些特殊的需求,可能就需要使用代理方法,具體使用我會在待會進行講解。先簡單的瞭解一下下面兩個方法。

// called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;

從官方文件和註釋我們可以看到上面這個代理方法會在點選事件發生之前被呼叫,返回NO的話就會阻止手勢事件,然後執行點選事件,如果返回YES則表示不執行點選事件而是執行手勢事件。

// called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;

從官方文件和註釋我們可以看到上面這個代理方法會在長按事件發生之前被呼叫,返回NO的話就會阻止手勢事件,然後執行長按事件,如果返回YES則表示不執行長按事件而是執行手勢事件。

使用技巧

首先需要知道事件響應的順序,對於UIButton,UISlider,UISwitch等繼承自UIControl的控制元件,都會率先響應事件,從而阻止了手勢事件。手勢也可以理解為一種特殊的層,所以在同一個View新增多個Tap手勢,則執行最後一個Tap手勢,對於TableView,Collection這樣的弱點選事件,系統優先響應Tap事件,如果需要響應Cell的點選事件,就需要實現代理方法。下面是具體的例子。
這裡寫圖片描述

子檢視不需要響應Tap事件

相信這樣的需求大家都遇到過,就是我在某個View裡面添加了Tap事件,然後View裡面添加了SecondView,我不想讓SecondView響應Tap事件。怎麼辦呢,一種方法就是獲取點選點的座標,然後對比座標是否在SecondView裡面,這樣做不是不可以,但是第一需要保證座標系統不變,比如SecondView發生了旋轉等可能會改變相關的座標參考系,第二這個座標還是會有一定的誤差和出入,尤其是對於邊界點的座標。比較好的做法是在子檢視上面也新增一個Tap,然後手勢的選擇子不做任何處理。

UITapGestureRecognizer *tapSuperGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapSuperView:)];
    tapSuperGesture.delegate = self;
    [self.view addGestureRecognizer:tapSuperGesture];

UITapGestureRecognizer *tapSecondGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapSecondView:)];
    [self.secondView addGestureRecognizer:tapSecondGesture];

- (void)tapSuperView:(UITapGestureRecognizer *)gesture {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"點選了SuperView" delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
    [alertView show];
}

- (void)tapSecondView:(UITapGestureRecognizer *)gesture {

}

這樣根據優先響應的順序,點選SecondView的時候tapSuperView 便不會響應。一些相關變化比如視圖裡面的某個區域不需要響應點選事件也可以用這樣的方法解決,新增一個子檢視進去就好。

響應TableViewCell事件

有時候會有這個蛋疼的需求,就是在檢視上面加了一個TableView,然後檢視添加了手勢。這樣就會出現UITapGesture和UITableViewCell的點選事件衝突。在前面我也介紹了需要實現委託方法來解決這個問題。只需要實現UIGestureRecognizerDelegate就可以解決這個問題。

@interface JQViewController ()<UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIView *secondView;
@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press {
    return NO;
}

後一個方法可要可不要,不過有時候為了防止有的使用者點選Cell喜歡一直壓在上面可能出現未知的問題,所以還是把這個加上為好。
設定代理不要忘記tapSuperGesture.delegate = self;

TableViewCell上面需要新增Label手勢事件

當然,最簡單的方法就是把Label直接修改為Button,事件一定會被響應,不過在有些需求裡面可能還是需要使用UILabel。這個時候會顯得有些麻煩,不過方法還是有。可以把手勢新增到TableView上面,然後通過判斷座標的方式。

- (void)tapTableView:(UITapGestureRecognizer *)gesture {

    CGPoint location = [gesture locationInView:self.tableView];
    //獲得當前座標對應的indexPath
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];

    if (indexPath) {
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        UILabel *label = nil;
        for (UIView *view in cell.subviews) {
            if ([view isKindOfClass:[UILabel class]]&& (view.tag == 1000)) {
                label = (UILabel *)view;
            }
        }

        //獲取手勢在當前Cell中位置
        if (!label) return;

        //判斷手勢在不在Lable中
        if (CGRectContainsPoint(label.frame, point)) {
            NSLog(@"點選了label");
        }
    }

}