iOS事件機(jī)制(點(diǎn)擊暇务、手勢(shì)、UIControl)

一.觸摸怔软、事件垦细、響應(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)方法峦萎。

touchesMethod.jpg

注意:對(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)者鏈向后傳遞。

UIReponderChain.png
nextResponder.png

總結(jié):
1)view的nextResponder片仿,是viewController的rootView纹安,則是viewController,否則為superView
2)viewController的nextResponder, 是window的rootViewController砂豌,則是window厢岂,否則為rootView的superView

  1. 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ò)程:


target-action.jpeg

手勢(shì)狀態(tài):


gestureState.png

離散手勢(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ò)程:


endTrackingWithTouch.jpg

四個(gè)重要識(shí)別方法是在touchesBegan盾剩、touchesMoved、touchedEnded替蔬、touchesCancelled里回調(diào)的告私。

action調(diào)用.jpg

推測(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)用棧:

調(diào)用棧.png

結(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消息

默認(rèn)調(diào)用路徑.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末见芹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蠢涝,更是在濱河造成了極大的恐慌玄呛,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件和二,死亡現(xiàn)場(chǎng)離奇詭異徘铝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)惯吕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)惕它,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人废登,你說(shuō)我怎么就攤上這事淹魄。” “怎么了堡距?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵甲锡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我羽戒,道長(zhǎng)缤沦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任易稠,我火速辦了婚禮缸废,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘驶社。我一直安慰自己企量,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布衬吆。 她就那樣靜靜地躺著,像睡著了一般绳泉。 火紅的嫁衣襯著肌膚如雪逊抡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音冒嫡,去河邊找鬼拇勃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛孝凌,可吹牛的內(nèi)容都是我干的方咆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蟀架,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瓣赂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起片拍,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤煌集,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后捌省,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體苫纤,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年纲缓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卷拘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡祝高,死狀恐怖栗弟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情褂策,我是刑警寧澤横腿,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站斤寂,受9級(jí)特大地震影響耿焊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜遍搞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一罗侯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧溪猿,春花似錦钩杰、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至依痊,卻和暖如春避除,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工瓶摆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凉逛,地道東北人磅轻。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓褥琐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親狂打。 傳聞我的和親對(duì)象是個(gè)殘疾皇子书斜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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