本文系轉(zhuǎn)載咒林,原文地址為iOS觸摸事件全家桶
UIControl是系統(tǒng)提供的能夠以target-action模式處理觸摸事件的控件垫竞,iOS中UIButton蛀序、UISegmentedControl、UISwitch等控件都是UIControl的子類引有。當UIControl跟蹤到觸摸事件時倦逐,會向其上添加的target發(fā)送事件以執(zhí)行action宫补。值得注意的是粉怕,UIConotrol是UIView的子類,因此本身也具備UIResponder應有的身份秉犹。
關(guān)于UIControl,此處介紹兩點:
- target-action執(zhí)行時機及過程
- 觸摸事件優(yōu)先級
target-action
- target:處理交互事件的對象
- action:處理交互事件的方式
UIControl作為能夠響應事件的控件崇堵,必然也需要待事件交互符合條件時才去響應鸳劳,因此也會跟蹤事件發(fā)生的過程。不同于UIResponder以及UIGestureRecognizer通過 touches
系列方法跟蹤涵紊,UIControl有其獨特的跟蹤方式:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)cancelTrackingWithEvent:(nullable UIEvent *)event;
乍一看幔摸,這4個方法和UIResponder的那4個方法幾乎吻合抚太,只不過UIControl只能接收單點觸控,因此接收的參數(shù)是單個UITouch對象尿贫。這幾個方法的職能也和UIResponder一致庆亡,用來跟蹤觸摸的開始、滑動拼缝、結(jié)束彰亥、取消。不過继阻,UIControl本身也是UIResponder废酷,因此同樣有 touches
系列的4個方法。事實上墨辛,UIControl的 Tracking
系列方法是在 touch
系列方法內(nèi)部調(diào)用的趴俘。比如 beginTrackingWithTouch
是在 touchesBegan
方法內(nèi)部調(diào)用的, 因此它雖然也是UIResponder太惠,但 touches
系列方法的默認實現(xiàn)和UIResponder本類還是有區(qū)別的垛叨。
當UIControl跟蹤事件的過程中,識別出事件交互符合響應條件敛纲,就會觸發(fā)target-action進行響應剂癌。UIControl控件通過 addTarget:action:forControlEvents:
添加事件處理的target和action,當事件發(fā)生時旁壮,UIControl通知target執(zhí)行對應的action谐檀。說是“通知”其實很籠統(tǒng)桐猬,事實上這里有個action傳遞的過程。當UIControl監(jiān)聽到需要處理的交互事件時溃肪,會調(diào)用 sendAction:to:forEvent:
將target惫撰、action以及event對象發(fā)送給全局應用,Application對象再通過 sendAction:to:from:forEvent:
向target發(fā)送action扼雏。
因此呢蛤,可以通過重寫UIControl的 sendAction:to:forEvent:
或 sendAction:to:from:forEvent:
自定義事件執(zhí)行的target及action棍郎。
另外银室,若不指定target励翼,即 addTarget:action:forControlEvents:
時target傳空汽抚,那么當事件發(fā)生時伯病,Application會在響應鏈上從上往下尋找能響應action的對象。官方說明如下:
If you specify
nil
for the target object, the control searches the responder chain for an object that defines the specified action method.
觸摸事件優(yōu)先級
當原本關(guān)系已經(jīng)錯綜復雜的UIGestureRecognizer和UIResponder之間又冒出一個UIControl惭蟋,又會摩擦出什么樣的火花呢药磺?
In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer.This applies only to gesture recognition that overlaps the default action for a control, which includes:
- A single finger single tap on a
UIButton
,UISwitch
,UIStepper
,UISegmentedControl
, andUIPageControl
.- A single finger swipe on the knob of a
UISlider
, in a direction parallel to the slider.- A single finger pan gesture on the knob of a
UISwitch
, in a direction parallel to the switch.
簡單理解:UIControl會阻止父視圖上的手勢識別器行為癌佩,也就是UIControl處理事件的優(yōu)先級比UIGestureRecognizer高围辙,但前提是相比于父視圖上的手勢識別器。
-
預置場景:在BlueView上添加一個button姚建,同時給button添加一個target-action事件怎囚。
- 示例一:在BlueView上添加點擊手勢識別器
- 示例二:在button上添加手勢識別器
操作方式:單擊button
-
測試結(jié)果:示例一中,button的target-action響應了單擊事件桥胞;示例二中恳守,BlueView上的手勢識別器響應了事件。過程日志打印如下:
//示例一 -[CLTapGestureRecognizer touchesBegan:withEvent:] -[CLButton touchesBegan:withEvent:] -[CLButton beginTrackingWithTouch:withEvent:] -[CLTapGestureRecognizer touchesEnded:withEvent:] after called state = 5 -[CLButton touchesEnded:withEvent:] -[CLButton endTrackingWithTouch:withEvent:] 按鈕點擊
//示例二 -[CLTapGestureRecognizer touchesBegan:withEvent:] -[CLButton touchesBegan:withEvent:] -[CLButton beginTrackingWithTouch:withEvent:] -[CLTapGestureRecognizer touchesEnded:withEvent:] after called state = 3 手勢觸發(fā) -[CLButton touchesCancelled:withEvent:] -[CLButton cancelTrackingWithEvent:]
原因分析:點擊button后贩虾,事件先傳遞給手勢識別器催烘,再傳遞給作為hit-tested view存在的button(UIControl本身也是UIResponder,這一過程和普通事件響應者無異)缎罢。示例一中伊群,由于button阻止了父視圖BlueView中的手勢識別器的識別,導致手勢識別器識別失斀⑹肌(狀態(tài)為failed 枚舉值為5),button完全接手了事件的響應權(quán)咽袜,事件最終由button響應丸卷;示例二中,button未阻止其本身綁定的手勢識別器的識別询刹,因此手勢識別器先識別手勢并識別成功(狀態(tài)為ended 枚舉值為3)谜嫉,而后通知Application取消響應鏈對事件的響應萎坷,因為
touchesCancelled
被調(diào)用,同時cancelTrackingWithEvent
跟著調(diào)用沐兰,因此button的target-action得不到執(zhí)行哆档。其他:經(jīng)測試,若示例一中的手勢識別器設置
cancelsTouchesInView
為NO住闯,手勢識別器和button都能響應事件瓜浸。也就是說這種情況下,button不會阻止父視圖中手勢識別器的識別比原。結(jié)論:UIControl比其父視圖上的手勢識別器具有更高的事件響應優(yōu)先級斟叼。
TODO:
上述過程中,手勢識別器在執(zhí)行touchesEnded時是根據(jù)什么將狀態(tài)置為ended還是failed的春寿?即根據(jù)什么判斷應當識別成功還是識別失斃噬?
糾正 2017.11.17
以上所述UIControl的響應優(yōu)先級比手勢識別器高的說法不準確绑改,準確地說只適用于系統(tǒng)提供的有默認action操作的UIControl谢床,例如UIbutton、UISwitch等的單擊厘线,而對于自定義的UIControl识腿,經(jīng)驗證,響應優(yōu)先級比手勢識別器低造壮。讀者可自行驗證渡讼,感謝 @閆仕偉 同學的糾正。