(六)手勢(shì)識(shí)別器 UIGestureRecognizer

本文系轉(zhuǎn)載,原文地址為iOS觸摸事件全家桶

關(guān)于手勢(shì)識(shí)別器即 UIGestureRecognizer 本身的使用不是本文要所討論的內(nèi)容,按下不表。此處要探討的是:手勢(shì)識(shí)別器與UIResponder的聯(lián)系棍辕。

附: UIGestureRecognizer 子類

離散型手勢(shì)

持續(xù)性手勢(shì)

事實(shí)上,手勢(shì)分為離散型手勢(shì)(discrete gestures)和持續(xù)型手勢(shì)(continuous gesture)还绘。系統(tǒng)提供的離散型手勢(shì)包括點(diǎn)按手勢(shì)(UITapGestureRecognizer)和輕掃手勢(shì)(UISwipeGestureRecognizer)楚昭,其余均為持續(xù)型手勢(shì)。

兩者主要區(qū)別在于狀態(tài)變化過程:

  • 離散型:
    識(shí)別成功:Possible —> Recognized
    識(shí)別失斉那辍:Possible —> Failed

  • 持續(xù)型:
    完整識(shí)別:Possible —> Began —> [Changed] —> Ended
    不完整識(shí)別:Possible —> Began —> [Changed] —> Cancel

離散型手勢(shì)

先拋開上面的場(chǎng)景抚太,看一個(gè)簡(jiǎn)單的demo。

image

控制器的視圖上add了一個(gè)View記為YellowView昔案,并綁定了一個(gè)單擊手勢(shì)識(shí)別器尿贫。

// LXFViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionTap)];
    [self.view addGestureRecognizer:tap];
}
- (void)actionTap{
    NSLog(@"View Taped");
}

單擊YellowView,日志打印如下:

-[YellowView touchesBegan:withEvent:]
View Taped
-[YellowView touchesCancelled:withEvent:]

從日志上看出YellowView最后Cancel了對(duì)觸摸事件的響應(yīng)爱沟,而正常應(yīng)當(dāng)是觸摸結(jié)束后帅霜,YellowView的 touchesEnded:withEvent: 的方法被調(diào)用才對(duì)。另外呼伸,期間還執(zhí)行了手勢(shì)識(shí)別器綁定的action 身冀。我從官方文檔找到了這樣的解釋:

A window delivers touch events to a gesture recognizer before it delivers them to the hit-tested view attached to the gesture recognizer. Generally, if a gesture recognizer analyzes the stream of touches in a multi-touch sequence and doesn’t recognize its gesture, the view receives the full complement of touches. If a gesture recognizer recognizes its gesture, the remaining touches for the view are cancelled.The usual sequence of actions in gesture recognition follows a path determined by default values of the cancelsTouchesInView, delaysTouchesBegan, delaysTouchesEnded properties.

大致理解是钝尸,Window在將事件傳遞給hit-tested view之前,會(huì)先將事件傳遞給相關(guān)的手勢(shì)識(shí)別器并由手勢(shì)識(shí)別器優(yōu)先識(shí)別搂根。若手勢(shì)識(shí)別器成功識(shí)別了事件,就會(huì)取消hit-tested view對(duì)事件的響應(yīng)剩愧;若手勢(shì)識(shí)別器沒能識(shí)別事件仁卷,hit-tested view才完全接手事件的響應(yīng)權(quán)穴翩。

一句話概括:手勢(shì)識(shí)別器比UIResponder具有更高的事件響應(yīng)優(yōu)先級(jí)!锦积!

按照這個(gè)解釋,Window在將事件傳遞給hit-tested view即YellowView之前背蟆,先傳遞給了控制器根視圖上的手勢(shì)識(shí)別器。手勢(shì)識(shí)別器成功識(shí)別了該事件哮幢,通知Application取消YellowView對(duì)事件的響應(yīng)带膀。

然而看日志,卻是YellowView的 touchesBegan:withEvent: 先調(diào)用了垛叨,既然手勢(shì)識(shí)別器先響應(yīng)柜某,不應(yīng)該上面的action先執(zhí)行嗎,這又怎么解釋还棱?事實(shí)上這個(gè)認(rèn)知是錯(cuò)誤的惭等。手勢(shì)識(shí)別器的action的調(diào)用時(shí)機(jī)(即此處的 actionTap)并不是手勢(shì)識(shí)別器接收到事件的時(shí)機(jī),而是手勢(shì)識(shí)別器成功識(shí)別事件后的時(shí)機(jī)辞做,即手勢(shì)識(shí)別器的狀態(tài)變?yōu)?a href="apple-reference-documentation://hcCvvcGh5U" target="_blank" rel="nofollow">UIGestureRecognizerStateRecognized。因此從該日志中并不能看出事件是優(yōu)先傳遞給手勢(shì)識(shí)別器的稚补,那該怎么證明Window先將事件傳遞給了手勢(shì)識(shí)別器框喳?

要解決這個(gè)問題厦坛,只要知道手勢(shì)識(shí)別器是如何接收事件的乍惊,然后在接收事件的方法中打印日志對(duì)比調(diào)用時(shí)間先后即可润绎。說起來你可能不信,手勢(shì)識(shí)別器對(duì)于事件的響應(yīng)也是通過這4個(gè)熟悉的方法來實(shí)現(xiàn)的呢蛤。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

需要注意的是,雖然手勢(shì)識(shí)別器通過這幾個(gè)方法來響應(yīng)事件棍郎,但它并不是UIResponder的子類顾稀,相關(guān)的方法聲明在 UIGestureRecognizerSubclass.h 中坝撑。

這樣一來粮揉,我們便可以自定義一個(gè)單擊手勢(shì)識(shí)別器的類扶认,重寫這幾個(gè)方法來監(jiān)聽手勢(shì)識(shí)別器接收事件的時(shí)機(jī)。創(chuàng)建一個(gè)UITapGestureRecognizer的子類狱从,重寫響應(yīng)事件的方法叠纹,每個(gè)方法中調(diào)用父類的實(shí)現(xiàn),并替換demo中的手勢(shì)識(shí)別器与涡。另外需要在.m文件中引入 import <UIKit/UIGestureRecognizerSubclass.h> 持偏,因?yàn)橄嚓P(guān)方法聲明在該頭文件中。

// LXFTapGestureRecognizer (繼承自UITapGestureRecognizer)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
    [super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
    [super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
    [super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s",__func__);
    [super touchesCancelled:touches withEvent:event];
}

現(xiàn)在酌畜,再次點(diǎn)擊YellowView桥胞,日志如下:

-[LXFTapGestureRecognizer touchesBegan:withEvent:]
-[YellowView touchesBegan:withEvent:]
-[LXFTapGestureRecognizer touchesEnded:withEvent:]
View Taped
-[YellowView touchesCancelled:withEvent:]

很明顯,確實(shí)是手勢(shì)識(shí)別器先接收到了事件井誉。之后手勢(shì)識(shí)別器成功識(shí)別了手勢(shì)整胃,執(zhí)行了action,再由Application取消了YellowView對(duì)事件的響應(yīng)在岂。

Window怎么知道要把事件傳遞給哪些手勢(shì)識(shí)別器蛮寂?

之前探討過Application怎么知道要把event傳遞給哪個(gè)Window,以及Window怎么知道要把event傳遞給哪個(gè)hit-tested view的問題及老,答案是這些信息都保存在event所綁定的touch對(duì)象上范抓。手勢(shì)識(shí)別器也是一樣的匕垫,event綁定的touch對(duì)象上維護(hù)了一個(gè)手勢(shì)識(shí)別器數(shù)組,里面的手勢(shì)識(shí)別器毫無疑問是在hit-testing的過程中收集的寞秃。打個(gè)斷點(diǎn)看一下touch上綁定的手勢(shì)識(shí)別器數(shù)組:

image

Window先將事件傳遞給這些手勢(shì)識(shí)別器偶惠,再傳給hit-tested view。一旦有手勢(shì)識(shí)別器成功識(shí)別了手勢(shì)堂淡,Application就會(huì)取消hit-tested view對(duì)事件的響應(yīng)扒腕。

持續(xù)型手勢(shì)

將上面Demo中視圖綁定的單擊手勢(shì)識(shí)別器用滑動(dòng)手勢(shì)識(shí)別器(UIPanGestureRecognizer)替換瘾腰。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(actionPan)];
    [self.view addGestureRecognizer:pan];
}
- (void)actionPan{
    NSLog(@"View panned");
}

在YellowView上執(zhí)行一次滑動(dòng):

image

日志打印如下:

-[YellowView touchesBegan:withEvent:]
-[YellowView touchesMoved:withEvent:]
-[YellowView touchesMoved:withEvent:]
-[YellowView touchesMoved:withEvent:]
View panned
-[YellowView touchesCancelled:withEvent:]
View panned
View panned
View panned
...

在一開始滑動(dòng)的過程中蹋盆,手勢(shì)識(shí)別器處在識(shí)別手勢(shì)階段硝全,滑動(dòng)產(chǎn)生的連續(xù)事件既會(huì)傳遞給手勢(shì)識(shí)別器又會(huì)傳遞給YellowView楞抡,因此YellowView的 touchesMoved:withEvent: 在開始一段時(shí)間內(nèi)會(huì)持續(xù)調(diào)用召廷;當(dāng)手勢(shì)識(shí)別器成功識(shí)別了該滑動(dòng)手勢(shì)時(shí),手勢(shì)識(shí)別器的action開始調(diào)用先紫,同時(shí)通知Application取消YellowView對(duì)事件的響應(yīng)筹煮。之后僅由滑動(dòng)手勢(shì)識(shí)別器接收事件并響應(yīng),YellowView不再接收事件本冲。

另外劫扒,在滑動(dòng)的過程中,若手勢(shì)識(shí)別器未能識(shí)別手勢(shì),則事件在觸摸滑動(dòng)過程中會(huì)一直傳遞給hit-tested view闷板,直到觸摸結(jié)束院塞。讀者可自行驗(yàn)證。

先總結(jié)一下手勢(shì)識(shí)別器與UIResponder對(duì)于事件響應(yīng)的聯(lián)系:

  1. 當(dāng)觸摸發(fā)生或者觸摸的狀態(tài)發(fā)生變化時(shí)县遣,Window都會(huì)傳遞事件尋求響應(yīng)汹族。
  2. Window先將綁定了觸摸對(duì)象的事件傳遞給觸摸對(duì)象上綁定的手勢(shì)識(shí)別器,再發(fā)送給觸摸對(duì)象對(duì)應(yīng)的hit-tested view夸政。
  3. 手勢(shì)識(shí)別器識(shí)別手勢(shì)期間榴徐,若觸摸對(duì)象的觸摸狀態(tài)發(fā)生變化,事件都是先發(fā)送給手勢(shì)識(shí)別器再發(fā)送給hit-test view耗帕。
  4. 手勢(shì)識(shí)別器若成功識(shí)別了手勢(shì)仿便,則通知Application取消hit-tested view對(duì)于事件的響應(yīng),并停止向hit-tested view發(fā)送事件探越;
  5. 若手勢(shì)識(shí)別器未能識(shí)別手勢(shì)钦幔,而此時(shí)觸摸并未結(jié)束,則停止向手勢(shì)識(shí)別器發(fā)送事件搀擂,僅向hit-test view發(fā)送事件卷玉。
  6. 若手勢(shì)識(shí)別器未能識(shí)別手勢(shì),且此時(shí)觸摸已經(jīng)結(jié)束威恼,則向hit-tested view發(fā)送end狀態(tài)的touch事件以停止對(duì)事件的響應(yīng)寝并。

手勢(shì)識(shí)別器的3個(gè)屬性

@property(nonatomic) BOOL cancelsTouchesInView;
@property(nonatomic) BOOL delaysTouchesBegan;
@property(nonatomic) BOOL delaysTouchesEnded;

cancelsTouchesInView

默認(rèn)為YES衬潦。表示當(dāng)手勢(shì)識(shí)別器成功識(shí)別了手勢(shì)之后,會(huì)通知Application取消響應(yīng)鏈對(duì)事件的響應(yīng)镀岛,并不再傳遞事件給hit-test view漂羊。
若設(shè)置成NO,表示手勢(shì)識(shí)別成功后不取消響應(yīng)鏈對(duì)事件的響應(yīng)稻据,事件依舊會(huì)傳遞給hit-test view
demo中設(shè)置:pan.cancelsTouchesInView = NO
滑動(dòng)時(shí)日志如下:

-[YellowView touchesBegan:withEvent:]
-[YellowView touchesMoved:withEvent:]
-[YellowView touchesMoved:withEvent:]
-[YellowView touchesMoved:withEvent:]
View panned
-[YellowView touchesMoved:withEvent:]
View panned
View panned
-[YellowView touchesMoved:withEvent:]
View panned
-[YellowView touchesMoved:withEvent:]

即便滑動(dòng)手勢(shì)識(shí)別器識(shí)別了手勢(shì)匆赃,Application也會(huì)依舊發(fā)送事件給YellowView今缚。

delaysTouchesBegan

默認(rèn)為 NO姓言。默認(rèn)情況下手勢(shì)識(shí)別器在識(shí)別手勢(shì)期間,當(dāng)觸摸狀態(tài)發(fā)生改變時(shí)囱淋,Application都會(huì)將事件傳遞給手勢(shì)識(shí)別器和hit-tested view餐塘;
若設(shè)置成YES,則表示手勢(shì)識(shí)別器在識(shí)別手勢(shì)期間税手,截?cái)嗍录枘桑床粫?huì)將事件發(fā)送給hit-tested view
設(shè)置pan.delaysTouchesBegan = YES
日志如下:

View panned
View panned
View panned
View panned

因?yàn)榛瑒?dòng)手勢(shì)識(shí)別器在識(shí)別期間兵扬,事件不會(huì)傳遞給YellowView口蝠,因此期間YellowView的 touchesBegan:withEvent:touchesMoved:withEvent: 都不會(huì)被調(diào)用亚皂;而后滑動(dòng)手勢(shì)識(shí)別器成功識(shí)別了手勢(shì)国瓮,也就獨(dú)吞了事件,不會(huì)再傳遞給YellowView禁漓。因此只打印了手勢(shì)識(shí)別器成功識(shí)別手勢(shì)后的action調(diào)用孵睬。

delaysTouchesEnded

默認(rèn)為YES。當(dāng)手勢(shì)識(shí)別失敗時(shí)秘狞,若此時(shí)觸摸已經(jīng)結(jié)束烁试,會(huì)延遲一小段時(shí)間(0.15s)再調(diào)用響應(yīng)者的 touchesEnded:withEvent:;
若設(shè)置成NO靖诗,則在手勢(shì)識(shí)別失敗時(shí)會(huì)立即通知Application發(fā)送狀態(tài)為end的touch事件給hit-tested view以調(diào)用 touchesEnded:withEvent: 結(jié)束事件響應(yīng)支示。

總結(jié):手勢(shì)識(shí)別器比響應(yīng)鏈具有更高的事件響應(yīng)優(yōu)先級(jí)。

下一篇 (七)UIControl

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市据途,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌位衩,老刑警劉巖熔萧,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佛致,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡感昼,警方通過查閱死者的電腦和手機(jī)罐脊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門萍桌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恃逻,你說我怎么就攤上這事⊥怪#” “怎么了矛市?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵尘盼,是天一觀的道長。 經(jīng)常有香客問我配紫,道長午阵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任植袍,我火速辦了婚禮于个,結(jié)果婚禮上暮顺,老公的妹妹穿的比我還像新娘。我一直安慰自己羽氮,他們只是感情好惫恼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布祈纯。 她就那樣靜靜地躺著,像睡著了一般掀淘。 火紅的嫁衣襯著肌膚如雪油昂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音安寺,去河邊找鬼。 笑死言秸,一個(gè)胖子當(dāng)著我的面吹牛迎捺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抄沮,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼叛买,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蹋订!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起难礼,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤蛾茉,失蹤者是張志新(化名)和其女友劉穎撩鹿,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體键思,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吼鳞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年叫搁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疾党。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雪位,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出香罐,到底是詐尸還是另有隱情时肿,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布港令,位于F島的核電站顷霹,受9級(jí)特大地震影響击吱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜朵纷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一永脓、第九天 我趴在偏房一處隱蔽的房頂上張望常摧。 院中可真熱鬧,春花似錦落午、人聲如沸溃斋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春瓷翻,著一層夾襖步出監(jiān)牢的瞬間割坠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工对妄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敢朱,地道東北人拴签。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像构灸,于是被迫代替她去往敵國和親岸梨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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

  • 本文主要講解iOS觸摸事件的一系列機(jī)制,涉及的問題大致包括: 觸摸事件由觸屏生成后如何傳遞到當(dāng)前應(yīng)用稿茉? 應(yīng)用接收觸...
    baihualinxin閱讀 1,203評(píng)論 0 9
  • 在iOS開發(fā)中經(jīng)常會(huì)涉及到觸摸事件芥炭。本想自己總結(jié)一下园蝠,但是遇到了這篇文章,感覺總結(jié)的已經(jīng)很到位彪薛,特此轉(zhuǎn)載怠蹂。作者:L...
    WQ_UESTC閱讀 5,996評(píng)論 4 26
  • 轉(zhuǎn)載: https://blog.csdn.net/qq871531334/article/details/822...
    NicooYang閱讀 1,583評(píng)論 0 9
  • 在開發(fā)過程中侨歉,大家或多或少的都會(huì)碰到令人頭疼的手勢(shì)沖突問題,正好前兩天碰到一個(gè)類似的bug幽邓,于是借著這個(gè)機(jī)會(huì)了解了...
    閆仕偉閱讀 5,305評(píng)論 2 23
  • 觸摸事件的生命周期 當(dāng)我們手指觸碰屏幕的那一刻牵舵,一個(gè)觸摸事件便產(chǎn)生了。經(jīng)過進(jìn)程間通信白修,觸摸事件被傳遞到合適的應(yīng)用之...
    Gintok閱讀 1,344評(píng)論 0 3