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í)際開發(fā)中的借鑒也不多,涉及多點(diǎn)觸摸使用手勢(shì)更好。

// 觸摸的各個(gè)階段狀態(tài) 
// 例如當(dāng)手指移動(dòng)時(shí),會(huì)更新phase屬性到UITouchPhaseMoved;
// 手指離屏后,更新到UITouchPhaseEnded
typedef NS_ENUM(NSInteger, UITouchPhase) {
    UITouchPhaseBegan,             // 開始接觸屏幕
    UITouchPhaseMoved,             // 在屏幕上移動(dòng)
    UITouchPhaseStationary,        // whenever a finger is touching the surface but hasn't moved since the previous event.自從上一次事件后手指在屏幕上沒有移動(dòng)
    UITouchPhaseEnded,             // 手指離開屏幕
    UITouchPhaseCancelled,         // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face) 手指雖然沒有離開屏幕,但是需要停止跟蹤手指
};

2.UIEvent

  • 觸摸生成觸摸事件,一個(gè)觸摸事件對(duì)應(yīng)一個(gè)UIEvent對(duì)象。UIEvent的type屬性標(biāo)識(shí)了事件的類型,事件有如下幾種類型:我們這里說(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覆蓋重寫了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的任何子類,例如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ò)重寫該屬性來(lái)決定傳遞規(guī)則。當(dāng)一個(gè)事件到來(lái)時(shí),第一響應(yīng)者沒有接收消息,則順著響應(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的子類。

值得注意的是,UIConotrol是UIView的子類,因此本身也具備UIResponder應(yīng)有的身份。

UIControl作為控件類的基類,它是一個(gè)抽象基類,我們不能直接使用UIControl類來(lái)實(shí)例化控件,它只是為控件子類定義一些通用的接口,并提供一些基礎(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)者,第二步處理將開始不同。

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.沒有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è)方法重寫,記錄打印過(guò)程,該view上添加tapGestureRecognized手勢(shì),該tapGestureRecognized也覆寫了這四個(gè)方法。

//
//  TestView.swift
//  TouchEventProj
//
//  Created by littledou on 2020/11/12.
//

import UIKit

class TestView : UIView { // 自定義UIView方法,覆寫響應(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方法,覆寫識(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ì)子類,因?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并沒有決定的優(yōu)先級(jí)。
從iOS6開始在控件的父視圖上面添加相應(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子類要覆蓋這四個(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)盡量不要覆蓋重寫UIResponder的touchesBegin、touchesMoved、touchesCancelled、touchesEnded這四個(gè)方法,如果需要覆蓋重寫,邏輯應(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容