一.觸摸怔软、事件垦细、響應(yīng)者、手勢(shì)挡逼、UIControl
1. UITouch
一個(gè)手指對(duì)應(yīng)一個(gè)UITouch對(duì)象括改,多個(gè)手指同時(shí)觸摸屏幕,對(duì)應(yīng)多個(gè)UITouch對(duì)象家坎。
系統(tǒng)會(huì)根據(jù)同一位置極短時(shí)間的點(diǎn)擊次數(shù)來(lái)判斷UITouch的點(diǎn)擊次數(shù)嘱能,比如第一次點(diǎn)擊生成了tapCount為1的一個(gè)UITouch,極短時(shí)間內(nèi)相同位置的點(diǎn)擊乘盖,系統(tǒng)會(huì)丟棄第一次生成的UITouch對(duì)象焰檩,重新生成一個(gè)UITouch對(duì)象,并且該tapCount為2(有說(shuō)法是說(shuō)只是更新第一次生成的UITouch的tapCount為2订框,本人測(cè)試的是Xcode12, iphone12, iOS 14.1模擬器環(huán)境析苫,是重新生成的一個(gè)UITouch)。
每個(gè)UITouch對(duì)象記錄了觸摸的一些信息,包括觸摸時(shí)間衩侥、位置国旷、階段、所處的視圖茫死、窗口等信息跪但。
UITouch有多個(gè)狀態(tài),每個(gè)狀態(tài)改變的時(shí)候會(huì)回調(diào)UIResponder的四個(gè)處理UITouch的響應(yīng)方法峦萎。
注意:對(duì)于一個(gè)手指的觸摸屡久,是UITouch每次狀態(tài)改變的時(shí)候都會(huì)回調(diào)UIResponder相對(duì)應(yīng)的處理方法。對(duì)于多個(gè)手指的觸摸爱榔,也許多個(gè)UITouch狀態(tài)的改變一起回調(diào)UIResponder的處理方法被环,也許每個(gè)UITouch狀態(tài)的改變都會(huì)回調(diào)UIResponder的處理方法,例如详幽,兩個(gè)點(diǎn)擊筛欢,可能只有一個(gè)touchesBegan的回調(diào),兩個(gè)touchesEnded的回調(diào).同時(shí)唇聘,多個(gè)UIControl狀態(tài)改變只有一次touchesBegan等方法回調(diào)的參數(shù)touches里touch的個(gè)數(shù)我測(cè)試的時(shí)候只有一個(gè)版姑,不要以為所有狀態(tài)改變的UITouch只有一次回調(diào)時(shí)都會(huì)放到touches參數(shù)里。關(guān)于多點(diǎn)觸摸的處理個(gè)人不建議在UITouch的響應(yīng)機(jī)制里去做處理迟郎,里面具體原理并不明朗剥险,實(shí)際開(kāi)發(fā)中的借鑒也不多,涉及多點(diǎn)觸摸使用手勢(shì)更好谎亩。
// 觸摸的各個(gè)階段狀態(tài)
// 例如當(dāng)手指移動(dòng)時(shí)炒嘲,會(huì)更新phase屬性到UITouchPhaseMoved;
// 手指離屏后匈庭,更新到UITouchPhaseEnded
typedef NS_ENUM(NSInteger, UITouchPhase) {
UITouchPhaseBegan, // 開(kāi)始接觸屏幕
UITouchPhaseMoved, // 在屏幕上移動(dòng)
UITouchPhaseStationary, // whenever a finger is touching the surface but hasn't moved since the previous event.自從上一次事件后手指在屏幕上沒(méi)有移動(dòng)
UITouchPhaseEnded, // 手指離開(kāi)屏幕
UITouchPhaseCancelled, // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face) 手指雖然沒(méi)有離開(kāi)屏幕夫凸,但是需要停止跟蹤手指
};
2.UIEvent
- 觸摸生成觸摸事件,一個(gè)觸摸事件對(duì)應(yīng)一個(gè)UIEvent對(duì)象阱持。UIEvent的type屬性標(biāo)識(shí)了事件的類(lèi)型夭拌,事件有如下幾種類(lèi)型:我們這里說(shuō)的事件指的是觸摸事件UIEventTypeTouches。
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches,
UIEventTypeMotion,
UIEventTypeRemoteControl,
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
};
- UIEvent對(duì)象中包含了觸發(fā)該事件的所有觸摸衷咽,可以通過(guò)觸摸對(duì)象allTouches屬性獲取鸽扁。比如兩次觸摸連續(xù)兩次點(diǎn)擊.
為了跟蹤UITouch的處理過(guò)程,我們自定義UIView覆蓋重寫(xiě)了touch的回調(diào)處理方法镶骗,如下:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(CACurrentMediaTime()) // 當(dāng)前時(shí)間
print(#function) // 打印函數(shù)名字
print(touches) // touches參數(shù)
print(event!) // event參數(shù)
super.touchesBegan(touches, with: event) // 系統(tǒng)默認(rèn)實(shí)現(xiàn)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print(CACurrentMediaTime())
print(#function)
print(touches)
print(event!)
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print(CACurrentMediaTime())
print(#function)
print(touches)
print(event!)
super.touchesCancelled(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print(CACurrentMediaTime())
print(#function)
print(touches)
print(event!)
super.touchesMoved(touches, with: event)
}
在自定義UIView上兩根手指觸摸連續(xù)點(diǎn)兩次桶现,,回調(diào)過(guò)程如下:
226116.34713520834
touchesBegan(_:with:)
[<UITouch: 0x1054046c0> phase: Began tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226116 touches: {(
<UITouch: 0x105501090> phase: Began tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {125, 745.5} previous location in window: {125, 745.5} location in view: {125, 745.5} previous location in view: {125, 745.5},
<UITouch: 0x1054046c0> phase: Began tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}
)}
226116.39147158334
touchesEnded(_:with:)
[<UITouch: 0x1054046c0> phase: Ended tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226116 touches: {(
<UITouch: 0x105501090> phase: Ended tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {125, 745.5} previous location in window: {125, 745.5} location in view: {125, 745.5} previous location in view: {125, 745.5},
<UITouch: 0x1054046c0> phase: Ended tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}
)}
226116.519107625
touchesBegan(_:with:)
[<UITouch: 0x1082029e0> phase: Began tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {265, 722} previous location in window: {265, 722} location in view: {265, 522} previous location in view: {265, 522}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
<UITouch: 0x1082029e0> phase: Began tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {265, 722} previous location in window: {265, 722} location in view: {265, 522} previous location in view: {265, 522}
)}
226116.5276584167
touchesMoved(_:with:)
[<UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {252.5, 717.5} previous location in window: {265, 722} location in view: {252.5, 517.5} previous location in view: {265, 522}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
<UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {252.5, 717.5} previous location in window: {265, 722} location in view: {252.5, 517.5} previous location in view: {265, 522},
<UITouch: 0x1054046c0> phase: Began tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {115, 753.5} previous location in window: {115, 753.5} location in view: {115, 753.5} previous location in view: {115, 753.5}
)}
226116.53451820835
touchesMoved(_:with:)
[<UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {249, 717.5} previous location in window: {252.5, 717.5} location in view: {249, 517.5} previous location in view: {252.5, 517.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
<UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {249, 717.5} previous location in window: {252.5, 717.5} location in view: {249, 517.5} previous location in view: {252.5, 517.5},
<UITouch: 0x1054046c0> phase: Stationary tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {115, 753.5} previous location in window: {115, 753.5} location in view: {115, 753.5} previous location in view: {115, 753.5}
)}
226116.57586620835
touchesEnded(_:with:)
[<UITouch: 0x1082029e0> phase: Ended tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {247.5, 718.5} previous location in window: {249, 717.5} location in view: {247.5, 518.5} previous location in view: {249, 517.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
<UITouch: 0x1082029e0> phase: Ended tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {247.5, 718.5} previous location in window: {249, 717.5} location in view: {247.5, 518.5} previous location in view: {249, 517.5},
<UITouch: 0x1054046c0> phase: Ended tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {115, 753.5} previous location in window: {115, 753.5} location in view: {115, 753.5} previous location in view: {115, 753.5}
)}
3.UIResponder
UIResponder是iOS中用于處理用戶事件的API鼎姊,可以處理觸摸事件骡和、按壓事件(3D touch)相赁、遠(yuǎn)程控制事件、硬件運(yùn)動(dòng)事件慰于∨タ疲可以通過(guò)touchesBegan、pressesBegan婆赠、motionBegan绵脯、remoteControlReceivedWithEvent等方法,獲取到對(duì)應(yīng)的回調(diào)消息休里。UIResponder不只用來(lái)接收事件蛆挫,還可以處理和傳遞對(duì)應(yīng)的事件,如果當(dāng)前響應(yīng)者不能處理妙黍,則轉(zhuǎn)發(fā)給其他合適的響應(yīng)者處理璃吧。
應(yīng)用程序通過(guò)響應(yīng)者來(lái)接收和處理事件,響應(yīng)者可以是繼承自UIResponder的任何子類(lèi)废境,例如UIView、UIViewController筒繁、UIApplication等噩凹。當(dāng)事件來(lái)到時(shí),系統(tǒng)會(huì)將事件傳遞給合適的響應(yīng)者毡咏,并且將其成為第一響應(yīng)者驮宴。
第一響應(yīng)者未處理的事件,將會(huì)在響應(yīng)者鏈中進(jìn)行傳遞呕缭,傳遞規(guī)則由UIResponder的nextResponder決定堵泽,可以通過(guò)重寫(xiě)該屬性來(lái)決定傳遞規(guī)則。當(dāng)一個(gè)事件到來(lái)時(shí)恢总,第一響應(yīng)者沒(méi)有接收消息迎罗,則順著響應(yīng)者鏈向后傳遞。
總結(jié):
1)view的nextResponder片仿,是viewController的rootView纹安,則是viewController,否則為superView
2)viewController的nextResponder, 是window的rootViewController砂豌,則是window厢岂,否則為rootView的superView
- window的nextResponder為UIApplication對(duì)象
4)UIApplication:若當(dāng)前應(yīng)用的app delegate是一個(gè)UIResponder對(duì)象,且不是UIView阳距、UIViewController或app本身塔粒,則UIApplication的nextResponder為app delegate
4.UIGestureRecognizer
Gesture Recognizer 是對(duì)底層事件處理的封裝,是為了讓使用者能夠更簡(jiǎn)單處理事件筐摘。
手勢(shì)分為離散型手勢(shì)(discrete gestures)和持續(xù)型手勢(shì)(continuous gesture)卒茬。
離散手勢(shì)識(shí)別(Discrete Gesture Recognizer)
UITapGestureRecognizer
UISwipeGestureRecognizer
UILongPressGestureRecognizer連續(xù)手勢(shì)識(shí)別(Continuous Gesture Recognizer)
UIPinchGestureRecognizer
UIPanGestureRecognizer
UIRotationGestureRecognizer
手勢(shì)響應(yīng)過(guò)程:
手勢(shì)狀態(tài):
離散手勢(shì)狀態(tài)改變過(guò)程:
Possible ----> Failed
Possible ----> Recognized
連續(xù)手勢(shì)狀態(tài)改變過(guò)程:
Possible ----> Began ----> [Changed] ----> Cancelled
Possible ----> Began ----> [Changed] ----> Ended
changed為可選狀態(tài)船老。
5.UIControl
UIControl是系統(tǒng)提供的能夠以target-action模式處理觸摸事件的控件,iOS中UIButton扬虚、UISegmentedControl努隙、UISwitch等控件都是UIControl的子類(lèi)。
值得注意的是辜昵,UIConotrol是UIView的子類(lèi)荸镊,因此本身也具備UIResponder應(yīng)有的身份。
UIControl作為控件類(lèi)的基類(lèi)堪置,它是一個(gè)抽象基類(lèi)躬存,我們不能直接使用UIControl類(lèi)來(lái)實(shí)例化控件,它只是為控件子類(lèi)定義一些通用的接口舀锨,并提供一些基礎(chǔ)實(shí)現(xiàn)岭洲,以在事件發(fā)生時(shí),預(yù)處理這些消息并將它們發(fā)送到指定目標(biāo)對(duì)象上坎匿。
Action的形式
@IBAction func doSomething()
@IBAction func doSomething(sender: UIButton)
@IBAction func doSomething(sender: UIButton, forEvent event: UIEvent)
識(shí)別重要方法:
- (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;
識(shí)別行為成功后發(fā)送消息:
- (void)sendActionsForControlEvents:(UIControlEvents)controlEvents;
UIControl的觸發(fā)過(guò)程:
四個(gè)重要識(shí)別方法是在touchesBegan盾剩、touchesMoved、touchedEnded替蔬、touchesCancelled里回調(diào)的告私。
推測(cè)是:endTrackingWithTouch調(diào)用后識(shí)別了行為,做標(biāo)記承桥,返回到touchesEnded后驻粟,判斷本UIControl是否易識(shí)別行為,調(diào)用行為回調(diào)凶异。
二.事件傳遞機(jī)制:
手勢(shì)蜀撑、UITouch、UIControl的處理機(jī)制都是不一樣的剩彬,特別是UIControl的處理機(jī)制仍然像個(gè)迷酷麦,只能根據(jù)想象進(jìn)行推測(cè)。手勢(shì)襟衰、UITouch贴铜、UIControl的處理機(jī)制第一步是相同的都是找到響應(yīng)者,第二步處理將開(kāi)始不同瀑晒。
1.找到第一個(gè)響應(yīng)者:
App接收到觸摸事件后绍坝,會(huì)被放入當(dāng)前應(yīng)用程序的UIApplication維護(hù)的事件隊(duì)列中.
由于事件一次只有一個(gè),但是能夠響應(yīng)的事件的響應(yīng)者眾多苔悦,所以這就存在一個(gè)尋找第一響應(yīng)者的過(guò)程轩褐。
調(diào)用方法,獲取到被點(diǎn)擊的視圖玖详,也就是第一響應(yīng)者把介。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
hitTest:withEvent:方法內(nèi)部會(huì)通過(guò)調(diào)用pointInside:這個(gè)方法勤讽,來(lái)判斷點(diǎn)擊區(qū)域是否在視圖上,是則返回YES拗踢,不是則返回NO脚牍。
2.沒(méi)有UIGestureRecognizer和UIControl的響應(yīng)
經(jīng)過(guò)Hit-Testing的過(guò)程后,UIApplication已經(jīng)知道了第一響應(yīng)者是誰(shuí)巢墅,接下來(lái)要做的事情就是:
1)將事件傳遞給第一響應(yīng)者
2)響應(yīng)者處理或者將事件沿著響應(yīng)鏈傳遞
3.有UIGestureRecognizer的響應(yīng)
自定義的view的touchesBegan诸狭、touchesMoved、touchesEnded君纫、touchedCancelled四個(gè)方法重寫(xiě)驯遇,記錄打印過(guò)程,該view上添加tapGestureRecognized手勢(shì)蓄髓,該tapGestureRecognized也覆寫(xiě)了這四個(gè)方法叉庐。
//
// TestView.swift
// TouchEventProj
//
// Created by littledou on 2020/11/12.
//
import UIKit
class TestView : UIView { // 自定義UIView方法,覆寫(xiě)響應(yīng)方法
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesCancelled(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesMoved(touches, with: event)
}
}
class TestGestureRecognizer : UITapGestureRecognizer { // 自定義UITapGestureRecognizer方法会喝,覆寫(xiě)識(shí)別手勢(shì)的方法
var tag:NSInteger?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesCancelled(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
print(NSStringFromClass(self.classForCoder))
print(#function)
super.touchesMoved(touches, with: event)
}
}
點(diǎn)擊view調(diào)用打印過(guò)程輸出:
TouchEventProj.TestGestureRecognizer
touchesBegan(_:with:)
TouchEventProj.TestView
touchesBegan(_:with:)
TouchEventProj.TestGestureRecognizer
touchesEnded(_:with:)
tapViewClicked(gestureRecognize:) // 手勢(shì)識(shí)別成功陡叠,調(diào)用手勢(shì)響應(yīng)方法
TouchEventProj.TestView
touchesCancelled(_:with:)
調(diào)用棧:
結(jié)合上面的輸出和調(diào)用棧,我們可能并不能明確的看出有手勢(shì)的時(shí)候點(diǎn)擊的過(guò)程肢执,不過(guò)如果你自己調(diào)試匾竿,是能得出如下結(jié)論的:
有手勢(shì)的時(shí)候點(diǎn)擊的大致調(diào)用過(guò)程:
1)hit-test找到第一響應(yīng)者,作為UITouch是能記住第一響應(yīng)者的蔚万。
2)更新響應(yīng)者和響應(yīng)者的上級(jí)視圖上所有的手勢(shì)狀態(tài)
3)同時(shí)第一響應(yīng)者處理UITouch的狀態(tài)回調(diào)
4)響應(yīng)者和響應(yīng)者的上級(jí)視圖上所有的手勢(shì)狀態(tài)中有識(shí)別成功的向UITouch發(fā)送cancelled消息
UIGestureRecognizer和UITouch的關(guān)系可以由UIGestureRecognizer的三個(gè)屬性影響:cancelsTouchesInView、delaysTouchesBegan临庇、delaysTouchesEnded反璃。
cancelsTouchesInView:默認(rèn)為YES。表示當(dāng)手勢(shì)識(shí)別器成功識(shí)別了手勢(shì)之后假夺,會(huì)通知Application取消響應(yīng)鏈對(duì)事件的響應(yīng)淮蜈,并不再傳遞事件給第一響應(yīng)者。若設(shè)置成NO已卷,表示手勢(shì)識(shí)別成功后不取消響應(yīng)鏈對(duì)事件的響應(yīng)梧田,事件依舊會(huì)傳遞給第一響應(yīng)者。
delaysTouchesBegan:默認(rèn)為NO侧蘸。默認(rèn)情況下手勢(shì)識(shí)別器在識(shí)別手勢(shì)期間裁眯,當(dāng)觸摸狀態(tài)發(fā)生改變時(shí),Application都會(huì)將事件傳遞給手勢(shì)識(shí)別器和第一響應(yīng)者讳癌;若設(shè)置成YES穿稳,則表示手勢(shì)識(shí)別器在識(shí)別手勢(shì)期間,截?cái)嗍录卫ぃ床粫?huì)將事件發(fā)送給第一響應(yīng)者逢艘。
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事件給第一響應(yīng)者以調(diào)用 touchesEnded:withEvent:結(jié)束事件響應(yīng)央拖。這個(gè)屬性的效果并不是很明顯祭阀,其實(shí)際使用中的作用也并不大。
3.有UIControl的響應(yīng)
這里主要是針對(duì)系統(tǒng)定義對(duì)UIControl對(duì)子類(lèi)爬泥,因?yàn)閁IControl對(duì)響應(yīng)原理并不是太明朗柬讨,只能根據(jù)現(xiàn)象進(jìn)行推測(cè)。
UIControl的響應(yīng)并不會(huì)影響UIResponder對(duì)點(diǎn)擊對(duì)響應(yīng)處理袍啡,處理UIControl對(duì)最重要對(duì)四個(gè)方法是:
- (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;
本身就是在UIResponder的UITouchesBegan踩官、UITouchesMoved、UITouchedEnded境输、UITouchesCancel四個(gè)回調(diào)中調(diào)用的蔗牡。
UIControl的響應(yīng)處理并不會(huì)影響UIResponder的響應(yīng)鏈的處理,但是UIControl會(huì)影響另一個(gè)UIControl嗅剖,子視圖的UIControl具有優(yōu)先級(jí)辩越。
4.有UIGestureRecognizer和UIControl的響應(yīng)
UIGestureRecognizer和UIControl并沒(méi)有決定的優(yōu)先級(jí)。
從iOS6開(kāi)始在控件的父視圖上面添加相應(yīng)的手勢(shì)信粮,控件就會(huì)控制阻止手勢(shì)行為黔攒,比如:
tap 手勢(shì)在 UIButton,UISwitch强缘,UIStepper督惰,UISegmentControl,UIPageControl旅掂;
swipe 手勢(shì)在 UISlider赏胚;
pan 手勢(shì)在 UISwitch;
其他可能是手勢(shì)優(yōu)于控件的行為商虐。
三.總結(jié)
UIResponder有touchesBegan等四個(gè)方法觉阅,默認(rèn)向superview傳遞。
所有需要自定義點(diǎn)擊處理邏輯的UIResponder子類(lèi)要覆蓋這四個(gè)方法秘车。
點(diǎn)擊事件由四個(gè)方法處理典勇。
UIButton的處理也是需要經(jīng)過(guò)這四個(gè)方法。
UIGestureRecognizer也有touchesBegan等四個(gè)方法叮趴。
手勢(shì)不在響應(yīng)鏈里痴柔,但是也會(huì)觀察它的view和subView的點(diǎn)擊豪嚎。
UIGestureRecognizer會(huì)影響UIResponder的四個(gè)響應(yīng)點(diǎn)擊的方法扔字。
默認(rèn)點(diǎn)擊事件響應(yīng)關(guān)鍵步驟說(shuō)明:
1)用戶手指點(diǎn)擊屏幕舵鳞,經(jīng)過(guò)系統(tǒng)傳遞到UIApplication, UIApplication通過(guò)hitTest:方法找到對(duì)應(yīng)UITouch發(fā)生的第一響應(yīng)者view
2)UIApplication更新手勢(shì)狀態(tài)抛虏,從第一響應(yīng)者上的手勢(shì)到其視圖層上所有先輩視圖上的手勢(shì)都會(huì)接收這個(gè)UITouch來(lái)更新手勢(shì)狀態(tài)
3)UIApplication將UITouch交給找到的第一響應(yīng)著view處理
4)UIApplication更新手勢(shì)狀態(tài)沸毁,識(shí)別成功后,會(huì)向UITouch的第一響應(yīng)者發(fā)送cancel方法
加上UIControl會(huì)讓過(guò)程變得復(fù)雜,關(guān)于UIControl的原理死遭,不清楚,也不敢妄下結(jié)論,依據(jù)網(wǎng)上和實(shí)際測(cè)試大致推斷:
1)它不會(huì)影響UITouch本身的響應(yīng)流程,但是會(huì)影響其他UIControl和UIGestureRecognizer的響應(yīng)
2)自定義的UIControl是和UITouch本身的響應(yīng)過(guò)程是一樣的
3)系統(tǒng)定義的UIControl和UIGestureRecognizer同一個(gè)優(yōu)先級(jí)塌鸯,誰(shuí)先識(shí)別出來(lái),另一個(gè)就out了,但是UIControl和UIGestureRecognizer有一點(diǎn)不同羹令,它并不會(huì)cancel UITouch的流程酒来。
關(guān)于UITouch、UIGestureRecognizer肪凛、UIControl之間影響說(shuō)明:
1)UITouch和UIGestureRecognizer:UIGestureRecognizer優(yōu)先級(jí)高于UITouch堰汉,由UIGestureRecognizer的三個(gè)參數(shù)cancelsTouchesInView、delaysTouchesBegan伟墙、delaysTouchesEnded決定對(duì)UITouch的影響翘鸭,默認(rèn)情況下,UIGestureRecognizer識(shí)別成功后戳葵,會(huì)向UITouch發(fā)送cancel
避免:
1)盡量不要覆蓋重寫(xiě)UIResponder的touchesBegin就乓、touchesMoved、touchesCancelled、touchesEnded這四個(gè)方法生蚁,如果需要覆蓋重寫(xiě)噩翠,邏輯應(yīng)該盡量簡(jiǎn)單,不宜做復(fù)雜的處理守伸,
2)不要自定義UIControl绎秒,直接使用系統(tǒng)定義的UIControl
3)UIControl上不要添加UIControl子視圖
4)不要依賴UIGestureRecognizer的delayTouchBegin和delayTouchEnded
5)不要自定義UIGestureRecognizer
參考文章:
1)iOS 事件(UITouch、UIControl尼摹、UIGestureRecognizer)傳遞機(jī)制
2)Touch Event Handing 教學(xué) — part 1