這篇文章側(cè)重于講解手勢識別、手勢沖突、以及手勢與響應(yīng)鏈的關(guān)系。在處理多個(gè)手勢識別場景,很容易出現(xiàn)手勢沖突的問題,比如,我們需要對圖片進(jìn)行單擊、長按、旋轉(zhuǎn)、縮放、拖動、翻轉(zhuǎn)等操作的時(shí)候,我們需要使用各種手勢來完成相應(yīng)的操作,這些手勢組合在一起很容易出現(xiàn)手勢沖突,而且當(dāng)引入手勢識別的時(shí)候,有時(shí)候你還會發(fā)現(xiàn)響應(yīng)者鏈似乎不符合規(guī)律了,這些都是處理手勢識別容易遇到的。
手勢
手勢識別在 iOS 中非常重要,它極大地豐富了用戶與iOS程序的交互方式。同時(shí)iOS提供的手勢API非常簡潔,在使用時(shí)一般的步驟為:1)創(chuàng)建手勢實(shí)例對象,2)添加手勢到需要識別的UIView中,注意每個(gè)手勢只對應(yīng)一個(gè)UIView。在 iOS 系統(tǒng)中主要有以下常見的手勢:
UIPanGestureRecognizer(拖動)
UIPinchGestureRecognizer(捏合)
UIRotationGestureRecognizer(旋轉(zhuǎn))
UITapGestureRecognizer(點(diǎn)按)
UILongPressGestureRecognizer(長按)
?UISwipeGestureRecognizer(輕掃)
UIScreenEdgePanGestureRecognizer (邊緣手勢)
響應(yīng)鏈
響應(yīng)者鏈?zhǔn)窍到y(tǒng)尋找事件響應(yīng)者的路徑。該路徑開始于firstResponder結(jié)束于單例application。事件首先會讓firstResponder對象去處理,如果它無法處理則會向其nextResponder對象轉(zhuǎn)發(fā)事件。當(dāng)所有對象都無法處理事件后將最后轉(zhuǎn)發(fā)到application處并最終忽略該事件。在UIKit中,UIApplication、UIView、UIViewController這幾個(gè)類都是直接繼承自UIResponder類。因此UIKit中的視圖、控件、視圖控制器,以及我們自定義的視圖及視圖控制器都有響應(yīng)事件的能力。這些對象通常被稱為響應(yīng)對象,是響應(yīng)鏈中的一個(gè)節(jié)點(diǎn)。

手勢沖突
- 1、如果一個(gè)手勢A的識別部分是另一個(gè)手勢B的子部分時(shí),默認(rèn)情況下A就會先識別,B就無法識別了。我們可以指定某個(gè)手勢執(zhí)行的前提是另一個(gè)手勢失敗才會識別執(zhí)行,這樣控制手勢識別的響應(yīng)順序。
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
- 2、如果同一視圖需要一次響應(yīng)多個(gè)手勢操作,可以實(shí)現(xiàn)下面的UIGestureRecognizerDelegate的代理方法,當(dāng)返回YES的時(shí)候,可以同時(shí)響應(yīng)多個(gè)手勢。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
同時(shí)響應(yīng)例子:
- (void)test1OnView:(UIView *)view
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
tap.delegate = self;
[view addGestureRecognizer:tap];
UILongPressGestureRecognizer *press = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
press.delegate = self;
[view addGestureRecognizer:press];
UISwipeGestureRecognizer *swip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
swip.delegate = self;
[view addGestureRecognizer:swip];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
pan.delegate = self;
[view addGestureRecognizer:pan];
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
rotation.delegate = self;
[view addGestureRecognizer:rotation];
}
- (void)onGestureRecognizerTrigger:(UIGestureRecognizer *)gestureRecognizer
{
NSLog(@"%@", [gestureRecognizer class]);
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
可以看到拖動的時(shí)候,還識別了輕掃手勢:
手勢識別[5246:277106] UIPanGestureRecognizer
手勢識別[5246:277106] UISwipeGestureRecognizer
手勢識別[5246:277106] UIPanGestureRecognizer
手勢識別[5246:277106] UIPanGestureRecognizer
手勢識別[5246:277106] UIPanGestureRecognizer
- 3、父視圖如果有手勢需要識別,子視圖同樣有相似觸摸事件需要處理,這時(shí)候就可能產(chǎn)生沖突。我們可以實(shí)現(xiàn) UIGestureRecognizerDelegate 的以下代理方法來解決相關(guān)沖突:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
在UIView中添加UITableView,當(dāng)UIView需要識別輕敲手勢的時(shí)候,這時(shí)UITableViewCell 點(diǎn)擊便失效,我們通過實(shí)現(xiàn)- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch代理方法,讓 UITableViewCell 可以被點(diǎn)擊。類似地在UIScrollView中添加 UITableView、UICollectionView 時(shí),UITableViewCell不能響應(yīng)相關(guān)點(diǎn)擊的時(shí)候,也可以通過這種方法解決。
- (void)test2OnView:(UIView *)view
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
tap.delegate = self;
tap.delegate = self;
[view addGestureRecognizer:tap];
UITableView *tableView = [[UITableView alloc] initWithFrame:view.bounds];
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:TableViewCellReuseIdentifier];
[view addSubview:tableView];
tableView.delegate = self;
tableView.dataSource = self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 50;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TableViewCellReuseIdentifier];
cell.textLabel.text = [NSString stringWithFormat:@"row = %ld", indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"didSelectRowAt index = %ld", indexPath.row);
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isKindOfClass:[UITableView class]]) {
return NO;
}
if ([NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"]) {
return NO;
}
return YES;
}
手勢與響應(yīng)者鏈
1、手勢與響應(yīng)者鏈有一些差別,觸摸事件首先會傳遞到手勢上,如果手勢識別成功,就會取消事件的繼續(xù)傳遞。如果手勢識別失敗,事件才會被響應(yīng)鏈處理。例子見 手勢沖突 的第三點(diǎn)。
2、對于 UIButton,UISwitch,UISegmentedControl,UIStepper、UIPageControl 進(jìn)行單擊操作,如果父視圖有輕敲手勢需要識別,依然會按照響應(yīng)鏈來處理,先響應(yīng)這些控件的單擊事件,這僅適用于與控件的默認(rèn)操作重疊的手勢識別。
- (void)test3OnView:(UIView *)view
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
[view addGestureRecognizer:tap];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 30)];
button.backgroundColor = [UIColor orangeColor];
[button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:button];
}
- (void)buttonTapped:(UIButton *)button
{
NSLog(@"%@", [button class]);
}
輸出結(jié)果為:
手勢識別[6618:352925] UIButton
3、如果子視圖和父視圖都有手勢需要識別,則按照firstResponder從子視圖到父視圖傳遞。
- (void)test4OnView:(UIView *)view
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger:)];
[view addGestureRecognizer:tap];
UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRecognizerTrigger1:)];
UIView *button = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 30)];
button.backgroundColor = [UIColor orangeColor];
[button addGestureRecognizer:tap1];
[view addSubview:button];
}
- (void)onGestureRecognizerTrigger1:(UIGestureRecognizer *)gestureRecognizer
{
NSLog(@"%s", __func__);
}
輸出結(jié)果為:
手勢識別[6680:355254] -[ViewController onGestureRecognizerTrigger1:]