手勢識別器是附加到視圖的對象浮禾,將低級別事件處理代碼轉(zhuǎn)換為更高級別的操作烦秩,它允許視圖以控件執(zhí)行的方式響應(yīng)操作味悄。 手勢識別器解釋觸摸以確定它們是否對應(yīng)于特定手勢,諸如滑動(dòng),捏合或旋轉(zhuǎn)炕婶,如果識別他們特定的手勢姐赡,他們發(fā)送動(dòng)作消息到目標(biāo)對象。 目標(biāo)對象通常是視圖的控制器柠掂,它響應(yīng)手勢项滑,如下圖所示。 這種設(shè)計(jì)模式既強(qiáng)大又簡單; 你可以動(dòng)態(tài)確定視圖響應(yīng)的動(dòng)作涯贞,你可以向視圖添加手勢識別器枪狂,而無需對視圖進(jìn)行子類化。
一宋渔、使用手勢識別器簡化事件處理
UIKit框架提供預(yù)定義的手勢識別器州疾,用來檢測常見手勢。 我們最好盡可能使用預(yù)定義的手勢識別器皇拣,因?yàn)樗鼈兊暮唵涡詼p少了必須編寫的代碼量严蓖。 此外,使用標(biāo)準(zhǔn)手勢識別器氧急,而不是自己寫手勢識別器颗胡,可以確保您的應(yīng)用程序按用戶期望的方式運(yùn)行。
如果您希望應(yīng)用程序識別獨(dú)特的手勢(例如復(fù)選標(biāo)記或旋轉(zhuǎn)動(dòng)作)吩坝,則可以創(chuàng)建自己的自定義手勢識別器毒姨。 要了解如何設(shè)計(jì)和實(shí)現(xiàn)自己的手勢識別器,請參閱創(chuàng)建自定義手勢識別器钉寝。
1. 內(nèi)置手勢識別器識別常見手勢
UIKit框架提供以下預(yù)設(shè)的手勢識別器弧呐,在設(shè)計(jì)app的時(shí)候可以考慮想要使用哪個(gè)手勢來滿足自己的需求。
手勢 | UIKit類 |
---|---|
點(diǎn)擊(任意個(gè)數(shù)點(diǎn)擊) | UITapGestureRecognizer |
縮放(用于縮放視圖) | UIPinchGestureRecognizer |
平移或拖動(dòng) | UIPanGestureRecognizer |
滑動(dòng)(任意方向) | UISwipeGestureRecognizer |
旋轉(zhuǎn)(手指沿相反方向移動(dòng)) | UIRotationGestureRecognizer |
長按(也稱觸摸并保持) | UILongPressGestureRecognizer |
2. 手勢識別器是附加到視圖上
每個(gè)手勢識別器與一個(gè)視圖相關(guān)聯(lián)嵌纲。 相比之下泉懦,視圖可以具有多個(gè)手勢識別器,因?yàn)閱蝹€(gè)視圖可以響應(yīng)許多不同的手勢疹瘦。 手勢識別器來識別在特定視圖中發(fā)生的觸摸,必須將手勢識別器附加到該視圖巡球。 當(dāng)用戶觸摸該視圖時(shí)言沐,手勢識別器要先于視圖對象接收發(fā)生的觸摸消息。 所以酣栈,手勢識別器可以代表視圖來響應(yīng)觸摸险胰。
3. 手勢觸發(fā)動(dòng)作消息
當(dāng)手勢識別器識別出其指定的手勢,它將向目標(biāo)對象發(fā)送動(dòng)作消息矿筝。要?jiǎng)?chuàng)建動(dòng)作識別器起便,你需要使用目標(biāo)對象和動(dòng)作進(jìn)行初始化。
3.1 離散和連續(xù)手勢
一個(gè)手勢不是離散的就是連續(xù)的。離散手勢(例如輕敲)發(fā)生一次榆综。 連續(xù)手勢則在一段時(shí)間內(nèi)發(fā)生妙痹,例如擠壓。 對于離散手勢鼻疮,手勢識別器向其目標(biāo)發(fā)送單個(gè)動(dòng)作消息怯伊。 連續(xù)手勢的手勢識別器則持續(xù)向其目標(biāo)發(fā)送動(dòng)作消息,直到多點(diǎn)觸摸序列結(jié)束判沟,如下圖所示耿芹。
二、響應(yīng)手勢識別器的事件
向你的app添加內(nèi)置的手勢識別器需要做三件事情:
-
創(chuàng)建并配置手勢識別器實(shí)例挪哄。
這一步包括指定目標(biāo)吧秕、動(dòng)作,并且有時(shí)候需要指定手勢識特定屬性(例如:numberOfTapsRequired點(diǎn)擊次數(shù))
將手勢識別器附加到視圖上迹炼。
實(shí)現(xiàn)處理手勢的動(dòng)作方法砸彬。
1. 使用Interface Builder來添加手勢識別器到App
在Xcode的Interface Builder中,向應(yīng)用程序添加手勢識別器的方式與向用戶界面添加任何對象的方式相同疗涉,即將手勢識別器從對象庫拖動(dòng)到視圖拿霉。 執(zhí)行此操作時(shí),手勢識別器會(huì)自動(dòng)附加到該視圖咱扣。 您可以檢查您的手勢識別器連接到哪個(gè)視圖绽淘,如果需要,更改nib文件中的連接闹伪。
創(chuàng)建手勢識別器對象后沪铭,需要?jiǎng)?chuàng)建和連接操作方法。 當(dāng)所連接的手勢識別器識別其手勢時(shí)偏瓤,會(huì)調(diào)用該方法杀怠。 如果您需要將手勢識別器進(jìn)行此操作方法之外的關(guān)聯(lián),則還應(yīng)為手勢識別器創(chuàng)建并連接outlet厅克。 如下代碼:
@interface YKNoteGestureRecognizerViewController ()
@property (strong, nonatomic) IBOutlet UITapGestureRecognizer *tapRecognizer;
@end
@implementation YKNoteGestureRecognizerViewController
- (IBAction)handleTapRecognizer:(UITapGestureRecognizer *)sender {
// implement the method
}
@end
2. 以編程方式添加手勢識別器
你可以通過分配和初始化具體的UIGestureRecognizer子類的實(shí)例(如UIPinchGestureRecognizer)以編程方式創(chuàng)建手勢識別器赔退。 初始化手勢識別器時(shí),請指定目標(biāo)對象和操作選擇器证舟。 通常硕旗,目標(biāo)對象是視圖的控制器。
如果以編程方式創(chuàng)建手勢識別器女责,則需要使用addGestureRecognizer:方法將其附加到視圖上漆枚。 下面代碼創(chuàng)建了單擊手勢識別器,指定需要一個(gè)輕擊以識別手勢抵知,然后將手勢識別器對象附加到視圖墙基。 通常软族,在視圖控制器的viewDidLoad方法中創(chuàng)建一個(gè)手勢識別器,如下所示:
- (void)viewDidLoad {
[super viewDidLoad];
// Create and initialize a tap gesture
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(respondToTapGesture:)];
// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;
// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];
// Do any additional setup after loading the view, typically from a nib
}
3. 響應(yīng)離散手勢
當(dāng)創(chuàng)建手勢識別器時(shí)残制,將識別器連接到操作方法立砸。 使用此操作方法來響應(yīng)手勢識別器的手勢。
響應(yīng)單擊手勢:
- (IBAction)handleTapRecognizer:(UITapGestureRecognizer *)sender {
NSLog(@"%s recognizer status:%ld", __PRETTY_FUNCTION__, sender.state);
CGPoint location = [sender locationInView:self.view];
[self drawImageForGestureRecognizer:sender atPoint:location];
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 0.0;
}];
}
每個(gè)手勢識別器都有自己的一組屬性痘拆。 例如仰禽,滑動(dòng)手勢識別器的direction屬性來確定用戶是向左還是向右滑動(dòng)。
- (IBAction)handleSwipeRecognizer:(UISwipeGestureRecognizer *)sender {
NSLog(@"%s recognizer status:%ld", __PRETTY_FUNCTION__, sender.state);
CGPoint location = [sender locationInView:self.view];
[self drawImageForGestureRecognizer:sender atPoint:location];
if (sender.direction == UISwipeGestureRecognizerDirectionLeft) {
location.x -= 100;
} else {
location.x += 100;
}
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 0.0;
self.imageView.center = location;
}];
}
4. 響應(yīng)連續(xù)手勢
連續(xù)手勢允許你的應(yīng)用程序響應(yīng)一個(gè)正在發(fā)生的手勢纺蛆。例如吐葵,你可以在用戶捏的時(shí)候進(jìn)行縮放或者允許用戶在屏幕上進(jìn)行拖拽。
- (IBAction)handleRotationRecognizer:(UIRotationGestureRecognizer *)sender {
NSLog(@"%s recognizer status:%ld", __PRETTY_FUNCTION__, sender.state);
CGPoint location = [sender locationInView:self.view];
CGAffineTransform transform = CGAffineTransformMakeRotation(sender.rotation);
self.imageView.transform = transform;
[self drawImageForGestureRecognizer:sender atPoint:location];
if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled) {
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 0.0;
self.imageView.transform = CGAffineTransformIdentity;
}];
}
}
三桥氏、定義手勢識別器如何交互
通常温峭,當(dāng)向應(yīng)用程序添加手勢識別器時(shí),你需要明確你希望手勢識別器如何與其他手勢交互或者與應(yīng)用程序中觸摸事件處理代碼進(jìn)行交互字支。 要做到這一點(diǎn)凤藏,你首先需要了解一點(diǎn)手勢識別器如何工作。
1. 手勢識別器在限定的狀態(tài)機(jī)中操作
手勢識別器以預(yù)定義的方式從一個(gè)狀態(tài)轉(zhuǎn)換到另一個(gè)狀態(tài)堕伪。 每個(gè)狀態(tài)可以基于它們是否滿足特定條件而移動(dòng)到幾個(gè)可能的下一個(gè)狀態(tài)中的一個(gè)揖庄。 精確的狀態(tài)機(jī)取決于手勢識別器是離散還是連續(xù)的而變化。所有手勢識別器都是從UIGestureRecognizerStatePossible開始欠雌。 他們分析收到的多點(diǎn)觸摸序列蹄梢,在分析期間,他們要么識別手勢富俄,要么未能識別禁炒。未能識別手勢意味著手勢識別器轉(zhuǎn)換到UIGestureRecognizerStateFailed狀態(tài)。
當(dāng)離散手勢識別器識別他的手勢霍比,手勢識別器從Possible狀態(tài)轉(zhuǎn)換到Recognized(UIGestureRecognizerStateRecognized)狀態(tài)幕袱,識別結(jié)束。
對于連續(xù)手勢悠瞬,當(dāng)首次識別手勢時(shí)们豌,手勢識別器從Possible狀態(tài)轉(zhuǎn)換到Began(UIGestureRecognizerStateBegan)狀態(tài)。然后浅妆,從Began轉(zhuǎn)換到Changed(UIGestureRecognizerStateChanged)玛痊,并且隨著手勢發(fā)生繼續(xù)從Changed轉(zhuǎn)變到Changed。當(dāng)用戶的最后一個(gè)手指從視圖抬起時(shí)狂打,手勢識別器轉(zhuǎn)換到結(jié)束狀態(tài)(UIGestureRecognizerStateEnded),并且識別完成混弥。請注意趴乡,Ended狀態(tài)是Recognized狀態(tài)的別名对省。
如果連續(xù)手勢的識別器決定手勢不再符合期望的模式,則其也可以從Change狀態(tài)轉(zhuǎn)變到Canceled(UIGestureRecognizerStateCancelled)狀態(tài)晾捏。
每當(dāng)手勢識別器改變狀態(tài)時(shí)蒿涎,手勢識別器向其目標(biāo)發(fā)送動(dòng)作消息,除非它轉(zhuǎn)變?yōu)镕ailed或Canceled惦辛。因此劳秋,當(dāng)離散手勢識別器狀態(tài)從Possible轉(zhuǎn)換為Recognized時(shí)僅發(fā)送單個(gè)動(dòng)作消息。連續(xù)手勢識別器在其改變狀態(tài)時(shí)發(fā)送許多動(dòng)作消息胖齐。
當(dāng)手勢識別器達(dá)到Recognized(或Ended)狀態(tài)時(shí)玻淑,它將其狀態(tài)重置為Possible。轉(zhuǎn)換回Possible狀態(tài)不會(huì)觸發(fā)操作消息呀伙。
2. 與其他手勢識別器交互
一個(gè)視圖可以附加多個(gè)手勢識別器补履。使用視圖的gestureRecognizers屬性來確定哪些手勢識別器附加到視圖。您還可以通過使用addGestureRecognizer:和removeGestureRecognizer:方法分別從視圖中添加或刪除手勢識別器來動(dòng)態(tài)更改視圖處理手勢的方式剿另。
當(dāng)視圖有多個(gè)手勢識別器附加到它箫锤,你可能想修改手勢識別器如何接收和分析觸摸事件解決沖突。默認(rèn)情況下雨女,手勢識別器沒有設(shè)置接收觸摸的先后順序谚攒,由于這個(gè)原因,觸摸可以每次以不同的順序傳遞到手勢識別器氛堕。您可以覆蓋以下默認(rèn)行為:
- 指定一個(gè)手勢識別器應(yīng)當(dāng)先于另一手勢識別器分析觸摸馏臭。
- 允許兩個(gè)手勢識別器同時(shí)操作。
- 防止手勢識別器分析觸摸岔擂。
使用UIGestureRecognizer的類方法位喂,委托方法和子類覆蓋的方法來實(shí)現(xiàn)這些行為。
2.1 為兩個(gè)手勢識別器聲明特定的順序
假設(shè)你想要識別滑動(dòng)和平移手勢乱灵,并且希望這兩個(gè)手勢觸發(fā)不同的操作塑崖。默認(rèn)情況下,當(dāng)用戶嘗試滑動(dòng)時(shí)痛倚,手勢會(huì)被解釋為平移规婆。這是因?yàn)榛瑒?dòng)手勢在被解釋滿足滑動(dòng)(離散手勢)的必要條件之前,被解釋為了滿足為平移(連續(xù)手勢)的必要條件蝉稳。
為了讓視圖識別滑動(dòng)和平移抒蚜,需要滑動(dòng)手勢識別器在平移手勢識別器之前分析觸摸事件。如果滑動(dòng)手勢識別器確定觸摸是滑動(dòng)耘戚,則平移手勢識別器不需要分析該觸摸嗡髓。如果滑動(dòng)手勢識別器確定觸摸不是滑動(dòng),則其移動(dòng)到Failed狀態(tài)收津,平移手勢識別器應(yīng)開始分析觸摸事件饿这。
在你希望延遲的手勢識別器上調(diào)用requireGestureRecognizerToFail:方法浊伙,可以在兩個(gè)手勢識別器之間指定這種關(guān)系。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib
[self.panRecognizer requireGestureRecognizerToFail:self.swipeRecognizer];
}
requireGestureRecognizerToFail:方法向接收方發(fā)送消息长捧,并指定在接收識別器可以開始之前必須失敗的一些其他識別器(otherGestureRecognizer)嚣鄙。當(dāng)它等待另一手勢識別器轉(zhuǎn)換到失敗狀態(tài)時(shí),接收識別器保持在Possible狀態(tài)串结。 如果另一手勢識別器失敗哑子,則接收識別器分析觸摸事件并移動(dòng)到其下一狀態(tài)。 另一方面肌割,如果另一手勢識別器轉(zhuǎn)變?yōu)镽ecognized或Began卧蜓,則接收識別器移動(dòng)到Failed狀態(tài)。
2.2 防止手勢識別器分析觸摸
你可以通過向手勢識別器添加委托對象來更改手勢識別器的行為声功。 UIGestureRecognizerDelegate協(xié)議提供了幾種方法烦却,可以防止手勢識別器分析觸摸∠劝停可以使用gestureRecognizer:shouldReceiveTouch:方法或gestureRecognizerShouldBegin:方法(都是UIGestureRecognizerDelegate協(xié)議的可選方法)其爵。
當(dāng)觸摸開始時(shí),如果您可以立即確定您的手勢識別器是否應(yīng)該考慮該觸摸伸蚯,請使用gestureRecognizer:shouldReceiveTouch:方法摩渺。每次有新的觸摸時(shí),都會(huì)調(diào)用此方法剂邮。該方法默認(rèn)值返回值為YES摇幻,返回NO,可以防止向手勢識別器通知發(fā)生了觸摸挥萌。該方法不改變手勢識別器的狀態(tài)绰姻。
- (void)viewDidLoad {
[super viewDidLoad];
// Add the delegate to the tap gesture recognizer
self.tapGestureRecognizer.delegate = self;
}
// Implement the UIGestureRecognizerDelegate method
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Determine if the touch is inside the custom subview
if ([touch view] == self.customSubview){
// If it is, prevent all of the delegate's gesture recognizers
// from receiving the touch
return NO;
}
return YES;
}
如果在決定手勢識別器是否應(yīng)分析觸摸之前你需要盡可能長時(shí)間等待,請使用gestureRecognizerShouldBegin:代理方法引瀑。 通常狂芋,如果你有與手勢識別器競爭自定義觸摸事件處理的UIView或者UIControl的子類,則使用此方法憨栽。 返回NO導(dǎo)致手勢識別器立即失敗帜矾,這允許其他觸摸處理繼續(xù)進(jìn)行。 當(dāng)手勢識別器嘗試從Possible狀態(tài)轉(zhuǎn)出時(shí)屑柔,如果手勢識別將阻止UIView或Control接收到觸摸屡萤,則調(diào)用該方法。
如果你的UIView或Controller不能是手勢識別器的委托掸宛,你可以使用gestureRecognizerShouldBegin:這個(gè)UIView方法死陆。 方法簽名和實(shí)現(xiàn)是一樣的。
2.3 允許同時(shí)手勢識別
默認(rèn)情況下唧瘾,兩個(gè)手勢識別器不能同時(shí)識別它們各自的手勢措译。 但是假設(shè)迫像,例如,你希望用戶能夠同時(shí)捏(pinch)和旋轉(zhuǎn)(rotate)視圖瞳遍。 您需要通過實(shí)現(xiàn)gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法(一個(gè)可選的UIGestureRecognizerDelegate協(xié)議的方法)來更改默認(rèn)行為。 當(dāng)一個(gè)手勢識別器對手勢的分析將阻止另一個(gè)手勢識別器識別其手勢時(shí)菌羽,或者反之亦然時(shí)掠械,調(diào)用該方法。 此方法默認(rèn)返回NO注祖。 當(dāng)您想要兩個(gè)手勢識別器同時(shí)分析其手勢時(shí)猾蒂,返回YES。
2.4 指定兩個(gè)手勢識別器之間的單向關(guān)系
如果你想控制兩個(gè)識別器如何相互交互是晨,你需要指定一個(gè)單向關(guān)系肚菠,你可以覆蓋canPreventGestureRecognizer:或canBePreventedByGestureRecognizer:子類方法返回NO(默認(rèn)為YES)。 例如罩缴,如果您想要旋轉(zhuǎn)手勢阻止縮放手勢蚊逢,但您不想要縮放手勢來阻止旋轉(zhuǎn)手勢,則可以指定:
[rotationGestureRecognizer canPreventGestureRecognizer:pinchGestureRecognizer];
并覆蓋旋轉(zhuǎn)手勢識別器的子類方法以返回NO箫章。 有關(guān)如何子類化UIGestureRecognizer的更多信息烙荷,請參閱創(chuàng)建自定義手勢識別器。
如果兩個(gè)手勢都不能阻止另一個(gè)檬寂,請使用gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法终抽,如允許同時(shí)手勢識別中所述。 默認(rèn)情況下桶至,縮放手勢阻止旋轉(zhuǎn),反之亦然,因?yàn)闊o法同時(shí)識別兩個(gè)手勢哈蝇。
3. 與其他用戶界面控件交互
在iOS 6.0及更高版本中甫男,默認(rèn)控制操作阻止重疊的手勢識別器行為。例如野瘦,按鈕的默認(rèn)操作是單擊描沟。如果你有一個(gè)單擊手勢識別器附加到一個(gè)按鈕的父視圖,用戶點(diǎn)擊按鈕鞭光,那么按鈕的動(dòng)作方法接收觸摸事件而不是手勢識別器吏廉。這僅適用于與控件的默認(rèn)操作與手勢識別重疊,其中包括:
- 單指在UIButton惰许,UISwitch席覆,UIStepper,UISegmentedControl和UIPageControl上單擊汹买。
- 單指在UISlider的旋鈕上滑動(dòng)佩伤,方向平行于slider聊倔。
- 單指在UISwitch 旋鈕上平移,方向平行于switch生巡。
如果您有這些控件的自定義子類耙蔑,并且想要更改默認(rèn)操作,請將手勢識別器直接附加到控件孤荣,而不是父視圖甸陌。然后,手勢識別器首先接收觸摸事件盐股。
四钱豁、手勢識別器解釋原始觸摸事件
到目前為止,已經(jīng)了解手勢以及應(yīng)用程序如何識別和響應(yīng)它們疯汁。 但是牲尺,要?jiǎng)?chuàng)建自定義手勢識別器或控制手勢識別器如何與視圖的觸摸事件處理的交互,您需要更具體地了解觸摸和事件幌蚊。
1. 事件包含當(dāng)前多點(diǎn)觸控序列的所有觸摸
在iOS中谤碳,觸摸是手指在屏幕上的存在或移動(dòng)。手勢具有一個(gè)或多個(gè)觸摸霹肝,由UITouch對象表示估蹄。例如,收縮 - 關(guān)閉手勢具有兩個(gè)觸摸 - 屏幕上的兩個(gè)手指從相反方向朝向彼此移動(dòng)沫换。
事件包含多點(diǎn)觸摸序列期間發(fā)生的所有觸摸臭蚁。多點(diǎn)觸摸序列在手指觸摸屏幕時(shí)開始,當(dāng)最后一個(gè)手指抬起時(shí)結(jié)束讯赏。當(dāng)手指移動(dòng)時(shí)垮兑,iOS將觸摸對象發(fā)送到事件。多點(diǎn)觸摸事件由UIEventTypeTouches類型的UIEvent對象表示漱挎。
每個(gè)觸摸對象只跟蹤一個(gè)手指系枪,并且持續(xù)時(shí)間和多點(diǎn)觸摸序列一樣長。在序列期間磕谅,UIKit跟蹤手指并更新觸摸對象的屬性私爷。這些屬性包括觸摸階段,其在視圖中的位置膊夹,其先前位置和其時(shí)間戳衬浑。
觸摸階段指示觸摸何時(shí)開始,其是移動(dòng)還是靜止放刨,以及何時(shí)結(jié)束(即工秩,當(dāng)手指不再觸摸屏幕時(shí))。如圖所示,應(yīng)用程序在觸摸的每個(gè)階段接收事件對象助币。
2. 應(yīng)用程序在觸摸處理方法中接收觸摸
在多點(diǎn)觸摸序列中浪听,當(dāng)有新的或者改變的觸摸時(shí)應(yīng)用程序發(fā)送這些消息:
- touchesBegan:withEvent: 當(dāng)一個(gè)或多個(gè)手指在屏幕上觸摸時(shí)調(diào)用
- touchesMoved:withEvent: 當(dāng)一個(gè)或多個(gè)手指移動(dòng)時(shí)調(diào)用
- touchesEnded:withEvent: 當(dāng)一個(gè)或多個(gè)手指不再觸摸屏幕時(shí)調(diào)用
- touchesCancelled:withEvent: 當(dāng)觸摸序列被諸如來電電話的系統(tǒng)事件取消時(shí)調(diào)用
這些方法與觸摸階段相關(guān)聯(lián); 例如,touchesBegan:withEvent:方法與UITouchPhaseBegan相關(guān)聯(lián)眉菱。 觸摸對象的觸摸階段存儲(chǔ)在其phase屬性中迹栓。
五、調(diào)節(jié)觸摸到視圖的傳遞路徑
有時(shí)俭缓,你希望視圖在手勢識別器之前接收到觸摸迈螟。 但是,在你更改觸摸到視圖的傳遞路徑之前尔崔,您需要了解默認(rèn)行為。 在簡單的情況下褥民,當(dāng)發(fā)生觸摸時(shí)季春,觸摸對象從UIApplication對象傳遞到UIWindow對象。 然后消返,在窗口將觸摸傳遞到視圖對象本身之前载弄,窗口首先向關(guān)聯(lián)到觸摸發(fā)生的視圖(或該視圖的父視圖)的所有手勢識別器發(fā)送觸摸。
1. 手勢識別器獲得第一個(gè)機(jī)會(huì)識別觸摸
窗口延遲觸摸對象傳遞到視圖撵颊,使得手勢識別可以首先分析觸摸宇攻。在延遲期間,如果手勢識別器識別觸摸手勢倡勇,則該窗口永遠(yuǎn)不會(huì)將觸摸對象傳遞到視圖逞刷,并且也取消一些前面發(fā)送到視圖的識別序列中的觸摸對象。
例如妻熊,如果你有一個(gè)需要兩指觸控的離散手勢識別器夸浅,傳送兩個(gè)獨(dú)立的觸摸對象。隨著觸摸發(fā)生扔役,觸摸對象從應(yīng)用程序?qū)ο髠鬟f給發(fā)生觸摸的視圖的窗口對象帆喇,并遵循以下順序:
- 窗體通過touchesBegan:withEvent:方法發(fā)送兩個(gè)在Began階段的觸摸對象到手勢識別器。手勢識別器還沒有識別手勢亿胸,所以其狀態(tài)為Possible坯钦。窗體也發(fā)送這些同樣的觸摸對象到手勢識別器所依附的視圖。
- 窗體通過touchesMoved:withEvent:方法發(fā)送兩個(gè)在Moved階段的觸摸對象到手勢識別器侈玄。識別器依然不檢測手勢婉刀,并且仍處于Possible狀態(tài)。然后窗口將這些觸摸發(fā)送到依附的視圖拗馒。
- 窗口通過touchesEnded:withEvent:方法發(fā)送一個(gè)在Ended階段的觸摸對象到手勢識別器路星。此觸摸對象不會(huì)為手勢產(chǎn)生足夠的信息,但窗口會(huì)從依附的視圖中扣留該觸摸對象(也就是說窗體不發(fā)送觸摸對象到視圖)。
- 窗口發(fā)送另一個(gè)在Ended階段的觸摸對象洋丐。手勢識別器現(xiàn)在識別其手勢呈昔,因此它將其狀態(tài)設(shè)置為Recognized。在第一個(gè)動(dòng)作消息發(fā)送之前友绝,視圖調(diào)用touchesCancelled:withEvent:方法使先前在Began和Moved階段發(fā)送的觸摸對象無效堤尾。Ended階段的觸摸被取消。
現(xiàn)在假設(shè)在最后一步中的手勢識別器決定它正在分析的這個(gè)多點(diǎn)觸摸序列不是它的手勢迁客。 它將其狀態(tài)設(shè)置為UIGestureRecognizerStateFailed郭宝。 然后,窗口將兩個(gè)Ended階段中的觸摸對象通過touchesEnded:withEvent:消息發(fā)送到依附的視圖掷漱。
連續(xù)手勢識別器遵循類似的順序粘室,除了在觸摸對象在到達(dá)Ended階段之前很有可能識別手勢。 在識別其手勢時(shí)卜范,它將其狀態(tài)設(shè)置為UIGestureRecognizerStateBegan(not Recognized)衔统。 窗口將多點(diǎn)觸摸序列中的所有后續(xù)觸摸對象發(fā)送到手勢識別器,但不發(fā)送到附加視圖海雪。
2. 影響觸摸到視圖的傳遞路徑
您可以更改UIGestureRecognizer幾個(gè)屬性的值锦爵,以某些方式更改默認(rèn)傳遞路徑。如果更改這些屬性的默認(rèn)值奥裸,您會(huì)得到以下行為差異:
-
delaysTouchesBegan(默認(rèn)為NO):通常情況下险掀,窗口將Began和Move階段中的觸摸對象發(fā)送到視圖和手勢識別器。將delaysTouchesBegan設(shè)置為YES可以防止窗口將Began階段中的觸摸對象傳遞到視圖湾宙。這確保當(dāng)手勢識別器識別手勢時(shí)樟氢,沒有將觸摸事件的任何部分遞送到其所附加的視圖。請謹(jǐn)慎設(shè)置此屬性侠鳄,因?yàn)樗赡軙?huì)使你感覺界面無響應(yīng)嗡害。
此設(shè)置與UIScrollView上的delaysContentTouches屬性類似;在這種情況下畦攘,當(dāng)在觸摸開始之后不久開始滾動(dòng)時(shí)霸妹,滾動(dòng)視圖對象的子視圖從不接收到觸摸,因此沒有視覺反饋知押。
-
delaysTouchesEnded(默認(rèn)為YES):當(dāng)此屬性設(shè)置為YES時(shí)叹螟,它確保視圖不會(huì)完成動(dòng)作,因?yàn)槭謩萆院罂赡苄枰∠ǘⅰ.?dāng)手勢識別器正在分析觸摸事件時(shí)罢绽,窗口不會(huì)傳遞Ended階段的觸摸對象到所附加的視圖。如果手勢識別器識別其手勢静盅,則取消觸摸對象良价。如果手勢識別器不識別其手勢寝殴,則窗口通過touchesEnded:withEvent:消息將這些對象遞送到視圖。將此屬性設(shè)置為NO允許視圖和手識別器同時(shí)分析Ended階段觸的觸摸對象明垢。
例如蚣常,視圖具有需要兩次點(diǎn)擊的點(diǎn)擊手勢識別器,并且用戶雙擊該視圖痊银。將屬性設(shè)置為YES抵蚊,視圖將獲得touchesBegan:withEvent:, touchesBegan:withEvent:溯革,touchesCancelled:withEvent:贞绳,和touchesCancelled:withEvent:。如果此屬性設(shè)置為NO致稀,視圖將獲取以下消息序列:touchesBegan:withEvent:冈闭, touchesEnded:withEvent:,touchesBegan:withEvent:抖单,和touchesCancelled:withEvent:拒秘,這意味著在touchesBegan:withEvent:,視圖可以識別雙擊臭猜。
如果手勢識別器檢測確定觸摸不是其手勢的一部分,則它可以將觸摸直接傳遞到其視圖押蚤。如果想這么做蔑歌,手勢識別器在其自身上調(diào)用ignoreTouch:forEvent:方法,傳入觸摸對象揽碘。
六次屠、創(chuàng)建自定義手勢識別器
為了實(shí)現(xiàn)自定義手勢識別器,首先在Xcode中創(chuàng)建UIGestureRecognizer的子類雳刺。然后在子類頭文件中引入U(xiǎn)IGestureRecognizerSubclass.h劫灶。
#import <UIKit/UIGestureRecognizerSubclass.h>
接下來,從UIGestureRecognizerSubclass.h的聲明中復(fù)制以下方法到你的頭文件掖桦。以下是需要在你的子類中需要覆寫的方法:
- (void)reset;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
這些方法與在應(yīng)用程序在觸摸處理方法中接收觸摸中所描述的觸摸事件處理方法具有相同的簽名和行為本昏。 在所有覆蓋的方法中,必須調(diào)用超類實(shí)現(xiàn)枪汪,即使該方法沒有具體實(shí)現(xiàn)內(nèi)容涌穆。
請注意,UIGestureRecognizerSubclass.h中的state屬性現(xiàn)在是readwrite雀久,而不是UIGestureRecognizer.h中的readonly宿稀。 你的子類通過為該屬性分配UIGestureRecognizerState常量來更改其狀態(tài)。
1. 為自定義手勢識別器實(shí)現(xiàn)觸摸事件處理方法
實(shí)現(xiàn)自定義手勢識別器的核心是四個(gè)方法:touchesBegan:withEvent:赖捌,touchesMoved:withEvent:祝沸,touchesEnded:withEvent:和touchesCancelled:withEvent:。在這些方法中,通過設(shè)置手勢識別器的狀態(tài)罩锐,將低級觸摸事件轉(zhuǎn)換為手勢識別奉狈。 下面代碼離散單觸式選擇標(biāo)記手勢創(chuàng)建了一個(gè)手勢識別器。 它記錄手勢的midpoint(向上點(diǎn)擊開始的點(diǎn))唯欣,以便客戶端可以獲得此值嘹吨。
此示例只有一個(gè)視圖,但大多數(shù)應(yīng)用程序有很多視圖境氢。 一般來說蟀拷,您應(yīng)該將觸摸位置轉(zhuǎn)換為屏幕的坐標(biāo)系,以便您可以正確識別跨多個(gè)視圖的手勢萍聊。
#import <UIKit/UIGestureRecognizerSubclass.h>
// Implemented in your custom subclass
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if ([touches count] != 1) {
self.state = UIGestureRecognizerStateFailed;
return;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
if (self.state == UIGestureRecognizerStateFailed) return;
//UIWindow *win = [self.view window];
//CGPoint nowPoint = [touches.anyObject locationInView:win];
CGPoint nowPoint = [touches.anyObject locationInView:self.view];
CGPoint prevPoint = [touches.anyObject previousLocationInView:self.view];
// strokeUp is a property
if (!self.strokeUp) {
// On downstroke, both x and y increase in positive direction
if (nowPoint.x >= prevPoint.x && nowPoint.y >= prevPoint.y) {
self.midPoint = nowPoint;
// Upstroke has increasing x value but decreasing y value
} else if (nowPoint.x >= prevPoint.x && nowPoint.y <= prevPoint.y) {
self.strokeUp = YES;
} else {
self.state = UIGestureRecognizerStateFailed;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if ((self.state == UIGestureRecognizerStatePossible) && self.strokeUp) {
self.state = UIGestureRecognizerStateRecognized;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
self.midPoint = CGPointZero;
self.strokeUp = NO;
self.state = UIGestureRecognizerStateFailed;
}
離散和連續(xù)手勢的狀態(tài)轉(zhuǎn)換是不同的问芬,如在手勢識別器在限定的狀態(tài)機(jī)中操作所描述的。 創(chuàng)建自定義手勢識別器時(shí)寿桨,通過為其分配相關(guān)狀態(tài)來指示其是離散還是連續(xù)此衅。上面代碼中的選擇標(biāo)記手勢識別器從不將狀態(tài)設(shè)置為Began或Changed,因?yàn)樗请x散的亭螟。
當(dāng)子類化手勢識別器時(shí)挡鞍,你需要做的最重要的事情是準(zhǔn)確地設(shè)置手勢識別器的狀態(tài)。 iOS需要知道手勢識別器的狀態(tài)预烙,以便手勢識別器按預(yù)期進(jìn)行交互墨微。 例如,如果要允許同時(shí)識別或需要手勢識別器失敗扁掸,iOS需要了解識別器的當(dāng)前狀態(tài)翘县。
有關(guān)創(chuàng)建自定義手勢識別器的更多信息,請參閱WWDC 2012:構(gòu)建高級手勢識別器谴分。
2. 重置手勢識別器的狀態(tài)
如果你的手勢識別器轉(zhuǎn)換為Recognized/Ended锈麸,Canceled或Failed,UIGestureRecognizer類將在手勢識別器轉(zhuǎn)換回Possible之前調(diào)用reset方法牺蹄。
實(shí)現(xiàn)reset方法以重置任何內(nèi)部狀態(tài)忘伞,使你的識別器準(zhǔn)備好進(jìn)行識別新的手勢。在手勢識別器從該方法返回之后沙兰,它不再接收正在進(jìn)行的觸摸更新虑省。
- (void)reset {
[super reset];
self.midPoint = CGPointZero;
self.strokeUp = NO;
}
示例:
https://github.com/wanyakun/YKNote/tree/master/YKNote/EventHandling