「iOS」————響應者鏈與事件傳遞鏈

iOS學習

  • 響應者鏈和事件傳遞鏈
    • 傳遞鏈:
      • hitTest:withEvent
      • **pointInside:withEvent**
    • 響應鏈
      • 第一響應者和最佳響應者
      • 觸摸事件(UITouch)
    • UIGestureRecognizer(手勢識別器)


響應者鏈和事件傳遞鏈

iOS事件的主要由:響應連和提交鏈構成。一般事件先通過提交鏈,提交下去。響應鏈,如果上層不能響應,那么一層通過響應鏈找到能響應的UIResponse

  • 響應連:由最基礎的view向系統提交,first view-> super view-> … -> view controller-> window-> Application->AppDelegate

  • 交付鏈:有系統向最上層view交付,Application-> window-> root view-> … ->first view

iOS中只有繼承了UIResponse的對象才能夠接受處理事件。UIResponse是響應對象的基類,定義了處理上述各種事件的接口。常見的子類有:UIViewUIViewControllerUIApplicationUIApplicationDelegate

穿透控件:

如果我們不想讓某個視圖響應事件,只需要重載 PointInside:withEvent:方法,讓此方法返回NO就行了.

若是view上有view1,view1上有view2,點擊view2,view2自己響應,點擊view1,view1不響應,只有view響應,也就是隔層傳遞

與加速器,陀螺儀和磁力計相關的運動事件不遵循響應者連,Core Motion將這些事件直接傳遞給你指定的對象

傳遞鏈:

事件傳遞流程

  • 發生觸摸事件后,系統會將該事件封裝成UIEvent對象加入到一個由UIApplication管理的事件隊列
  • UIApplication會從事件隊列中取出最前面的事件,并將事件分發下去以便處理,通常,先發送事件給應用程序的主窗口(keyWindow)
  • 主窗口會調用hitTest:withEvent: 方法沿著視圖層次結構從上到下進行傳遞最后在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個事件處理過程的第一步。
  • 找到合適的視圖控件后,就會調用視圖控件的touches方法(touchesBegan、touchesMoved、touchedEnded)來作具體的事件處理。

觸摸事件的傳遞是從父控件傳遞到子控件

也就是UIApplication->window->尋找處理事件最合適的view

觸摸事件的傳遞是從父控件傳遞到子控件,如果父控件不能接受觸摸事件,那么子控件就不可能接收到觸摸事件

img

上文中說到:要找到最合適的控件來處理事件,那么,如何找到呢?

找到最合適的控件:

  • 自己是否能接受觸摸事件?
  • 觸摸點是否在自己身上
    • 通過pointInside:withEvent 方法判斷觸摸點是否在自己身上。返回NO則不在自己身上,那就不再遍歷子控件,返回YES,代表在自己身上,那就繼續遍歷子控件,從后往前遍歷子控件,重復前面兩個步驟如果沒有符合條件的子控件,那么自己就是最適合處理的控件,找到最適合接受的控件后,調用控件touchesBegan,touchesMoved,touchedEnded的方法。
  • 從后往前遍歷子控件,重復前面的兩個步驟歐
  • 如果沒有符合條件的子控件,那么就自己最適合處理。

UIView不接收觸摸事件的三種情況:

  • userInteractionEnabled = NO隱藏
  • hidden = YES;
  • 透明:alpha = 0.0 ~ 0.01;

具體的尋找,使用了兩個方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

hitTest:withEvent

只要事件一傳遞給一個控件,這個控件就會調用他自己的hitTest:withEvent:方法

為了尋找并返回最合適的view(能夠響應時間的那個最合適的view)

以下是這個方法的實現邏輯

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {// 1.判斷窗口能否接收事件if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;// 2.判斷點在不在窗口上// 不在窗口上if ([self pointInside:point withEvent:event] == NO) return nil;// 3.從后往前遍歷子控件數組int count = (int)self.subviews.count;for (int i = count - 1; i >= 0; i--) {// 獲取子控件UIView *childView = self.subviews[i];// 坐標系的轉換,把窗口上的點轉換為子控件上的點// 把自己控件上的點轉換成子控件上的點CGPoint childP = [self convertPoint:point toView:childView];UIView *fitView = [childView hitTest:childP withEvent:event];if (fitView) {// 如果能找到最合適的viewreturn fitView;}}// 4.沒有找到更合適的view,也就是沒有比自己更合適的viewreturn self;}
  • 首先判斷該控件是否能接受事件
  • 在調用當前視圖的pointInside:withEvent: 方法判斷觸摸點是否在當前視圖內
  • 返回NO,則表示不在該視圖內,直接返回nil
  • 若返回YES,則向當前視圖的所有子視圖發送hitTest:withEvent: 消息,所有子視圖的遍歷順序是從最頂視圖一直到最低層視圖,即從 subviews 數組的末尾向前遍歷,直到有子視圖返回非空對象,或者全部子視圖遍歷完畢。
  • 若第一次有子視圖返回非空對象,則hitTest:withEvent:返回此對象,處理結束。
  • 若所有子視圖都返回空,則hitTest:withEvent:返回自身

不管這個控件能不能處理事件,也不管觸摸點在不在這個控件上,事件都會先傳遞給這個控件,隨后再調用hitTest:withEvent:方法

如果hitTest:withEvent:方法中返回nil,那么調用該方法的控件本身和其子控件都不是最合適的view,也就是在自己身上沒有找到更合適的view。那么最合適的view就是該控件的父控件。

其實該方法的邏輯很簡單,就是先判斷自己能不能響應,是不是在自己身上,不是則直接返回。是的話繼續往下遞歸判斷子事件,直到返回nil,nil之前的父控件則是最合適的控件。

pointInside:withEvent

該方法就是判斷點是不是在當前view上的。返回YES,代表點在方法調用者的坐標系上;返回NO,代表點不在方法調用者的坐標系上,那么方法調用者也就不能處理事件。

解決以下問題,點擊紅色區域時,按鈕并不會接受這個事件

在這里插入圖片描述

//TabBar
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{//將觸摸點坐標轉換到在CircleButton上的坐標CGPoint pointTemp = [self convertPoint:point toView:_CircleButton];//若觸摸點在CricleButton上則返回YESif ([_CircleButton pointInside:pointTemp withEvent:event]) {return YES;}//否則返回默認的操作return [super pointInside:point withEvent:event];
}

響應鏈

事件響應流程:

  • 如果找到最合適的控件來處理調用最合適的控件的touches…(touchesBegan、touchesMoved、touchedEnded)方法。
  • 如果調用了[super touch…],就會將事件順著響應者鏈往上傳遞,傳給上一個響應者,接著上一個響應者就會調用touches…方法。
  • 如果沒有找到合適的控件來處理事件,則將事件傳回來窗口,窗口不處理事件,將事件傳給 UIApplication。如果 UIApplication 不能處理事件,則將其丟棄。

響應者

在iOS中,不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收并處理事件,稱之為“響應者對象”。

UIApplication、UIViewController、UIView都繼承自UIResponder,因此它們都是響應者對象,都能夠接收并處理事件。

UIResponder提供了我們平時最常用的touchesBegan/touchesMoved/touchesEnded方法。此外還有如下幾個屬性比較重要:

  • isFirstResponder:判斷該View是否為第一響應者。
  • canBecomeFirstResponder:判斷該View是否可以成為第一響應者。
  • becomeFirstResponder:使該View成為第一響應者。
  • resignFirstResponder:取消View的第一響應者。

這里我們需要區分一下第一響應者和最佳響應者

第一響應者和最佳響應者

  1. 第一響應者 (First Responder):

    • 第一響應者是指當前能夠響應某個事件的第一個對象。
    • 通常情況下,當某個事件發生時,該事件首先被傳遞到第一響應者。
    • 第一響應者通常是用戶當前正在交互的視圖,比如用戶正在編輯的 UITextField或者點擊的 UIButton
  2. 最佳響應者(Best Responder):

    • 最佳響應者是指在響應者鏈上最適合處理某個事件的對象。
    • 當第一響應者無法完全處理某個事件時,該事件會沿著響應者鏈向上傳遞,直到找到最佳響應者。
    • 最佳響應者通常是能夠最完整地處理該事件的對象,比如包含第一響應者的視圖控制器。

一般來說,事件傳遞的目的就是為了讓我們找到第一響應者。那么我們該如何判斷是否為第一響應者呢?

  • 能夠響應觸摸事件
  • 觸摸點在自己身上
  • 沒有任何子視圖,或是所有子視圖都不在觸摸點上

補充知識:

對于UIWindow,若存在多個窗口,則優先詢問后顯示的窗口,這和控件的邏輯是一致的,優先詢問子視圖和后出現的控件

什么是上一個響應者?

如果當前這個view是控制器的view,那么控制器就是上一個響應者;

如果當前這個view不是控制器的view,那么父控件就是上一個響應者。

我們知道,響應者是從下往上傳的

響應者對于事件的操作方式:

響應者對于事件的攔截以及傳遞都是通過 touchesBegan:withEvent: 方法控制的,該方法的默認實現是將事件沿著默認的響應鏈往下傳遞。

響應者鏈條是什么

它是一種事件處理機制,由多個響應者對象連接起來的層次結構,使得事件可以沿著這些對象進行傳遞。利用響應者鏈條我們可以通過調用touches的super 方法,讓多個響應者同時響應該事件。

如何做到多個對象處理同一個事件

因為系統默認做法是把事件上拋給父控件,所以可以通過重寫自己的touches方法和父控件的touches方法來達到一個事件多個對象處理的目的

iOS中的各種事件

  • 觸摸事件
  • 加速計事件
  • 遠程控制事件

img

與加速器,陀螺儀和磁力計相關的運動事件不遵循響應者連,Core Motion將這些事件直接傳遞給你指定的對象

觸摸事件(UITouch)

保存著跟手指相關的信息,比如觸摸的位置、時間、階段。 當手指移動時,系統會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置。 當手指離開屏幕時,系統會銷毀相應的UITouch對象。

UITouch的常用屬性和方法

@property(nonatomic,readonly,retain) UIWindow *window;
//觸摸產生時所處的窗口
@property(nonatomic,readonly,retain) UIView *view;
//觸摸產生時所處的視圖
@property(nonatomic,readonly) NSUInteger tapCount;
//短時間內點按屏幕的次數,可以根據tapCount判斷單擊、雙擊或更多的點擊
@property(nonatomic,readonly) NSTimeInterval timestamp;
//記錄了觸摸事件產生或變化時的時間,單位是秒
@property(nonatomic,readonly) UITouchPhase phase;
//當前觸摸事件所處的狀態- (CGPoint)locationInView:(UIView *)view;
//返回值表示觸摸在view上的位置,這里返回的位置是針對view的坐標系的(以view的左上角為原點(0, 0));調用時傳入的view參數為nil的話,返回的是觸摸點在UIWindow的位置。
- (CGPoint)previousLocationInView:(UIView *)view;
//該方法記錄了前一個觸摸點的位置。

UIEvent

UIEvent:稱為事件對象,記錄事件產生的時刻和類型。 每產生一個事件,就會產生一個UIEvent對象。 UIEvent還提供了相應的方法可以獲得在某個view上面的觸摸對象(UITouch)。event綁定了touch對象

@property(nonatomic,readonly) UIEventType type;
@property(nonatomic,readonly) UIEventSubtype subtype;
//事件類型
@property(nonatomic,readonly) NSTimeInterval timestamp;
//事件產生的時間

觸摸過程:

一次完整的觸摸過程,會經歷一下三個狀態:

  • 觸摸開始:- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  • 觸摸移動:- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
  • 觸摸結束:- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

如果取消了這次觸摸,還會有一個觸摸取消方法:

  • 觸摸取消(可能會經歷):- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

這四個觸摸事件處理方法中,都有NSSet *touchesUIEvent *event兩個參數。

用戶用手指觸摸屏幕時,會創建一個與手指相關聯的UITouch對象。一根手指對應一個UITouch對象。

  • 一次完整的觸摸過程中,只會產生一個事件對象,4個觸摸方法都是同一個event參數。

  • 如果兩根手指同時觸摸一個view,那么view只會調用一次touchesBegan:withEvent:方法,touches參數中裝著2個UITouch對象。

  • 如果這兩根手指一前一后分開觸摸同一個view,那么view會分別調用2次touchesBegan:withEvent:方法,并且每次調用時的touches參數中只包含一個UITouch對象。

  • 根據touches中UITouch的個數可以判斷出是單點觸摸還是多點觸摸。

touches中存放的是UITouch對象,touch對象保存了觸摸所屬的window屬性和view屬性。同時event也綁定了touch對象。

UIGestureRecognizer(手勢識別器)

手勢識別器比UIResponder具有更高的事件響應優先級!!

UIGestureRecognizer能識別用戶在某個view上面做的一些常見手勢。該類是一個抽象類,定義了所有手勢的基本行為,使用它的子類才能處理具體的手勢。

事實上,手勢分為離散型手勢(discrete gestures)持續型手勢(continuous gesture)。系統提供的離散型手勢包括點按手勢(UITapGestureRecognizer)輕掃手勢(UISwipeGestureRecognizer),其余均為持續型手勢

UITapGestureRecognizer(敲擊) UIPinchGestureRecognizer(捏合,用于縮放) UIPanGestureRecognizer(拖拽)UISwipeGestureRecognizer(輕掃) UIRotationGestureRecognizer(旋轉) UILongPressGestureRecognizer(長按)

以下是UIGestureRecognizer定義

typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {// 沒有觸摸事件發生,所有手勢識別的默認狀態UIGestureRecognizerStatePossible,// 一個手勢已經開始但尚未改變或者完成時UIGestureRecognizerStateBegan,// 手勢狀態改變UIGestureRecognizerStateChanged,// 手勢完成UIGestureRecognizerStateEnded,// 手勢取消,恢復至Possible狀態UIGestureRecognizerStateCancelled, // 手勢失敗,恢復至Possible狀態UIGestureRecognizerStateFailed,// 識別到手勢識別UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};

手勢識別器會搶占UITableView的cell的點擊事件。
UITapGestureRecognizer 默認 cancelsTouchesInView = YES:識別成功后會調用 touchesCancelled,中斷 TableView 內部的點擊流程,didSelectRowAtIndexPath可能不觸發或只高亮不選擇。

總結

  • 觸摸發生時,系統內核生成觸摸事件,先由IOKit處理封裝成IOHIDEvent對象,通過IPC傳遞給系統進程SpringBoard,而后再傳遞給前臺APP處理。
  • 事件傳遞到APP內部時被封裝成開發者可見的UIEvent對象,先經過hit-testing尋找第一響應者,而后由Window對象將事件傳遞給hit-tested view,并開始在響應鏈上的傳遞。
  • UIRespnder、UIGestureRecognizer、UIControl,籠統地講,事件響應優先級依次遞增。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/95297.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/95297.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/95297.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

修復圖像、視頻和3D場景的AI工具–Inpaint Anything

TL; DR&#xff1a;用戶可以通過單擊來選擇圖像中的任何對象。借助強大的視覺模型&#xff0c;例如SAM、LaMa和穩定擴散 (SD)&#xff0c;Inpaint Anything能夠順利地移除對象&#xff08;即Remove Anything&#xff09;。此外&#xff0c;在用戶輸入文本的提示下&#xff0c;I…

java -jar xxx.jar 提示xxx.jar中沒有主清單屬性報錯解決方案

xxx.jar 中沒有主清單屬性 &#xff08;no main manifest attribute&#xff09;解決方案 java -jar xxx.jar 提示xxx.jar中沒有主清單屬性報錯解決方案 這個錯通常出現在你用 java -jar xxx.jar 啟動&#xff0c;但 JAR 的 META-INF/MANIFEST.MF 里沒有 Main-Class 條目&#…

Myqsl建立庫表練習

目錄 一、windows中選擇一種方式安裝Mysql8.0 二、新建產品庫mydb6_product 1. 新建3張表如下&#xff1a; 1&#xff09;employees表 2&#xff09;orders表 3&#xff09;invoices表 三、新建員工庫mydb8_worker&#xff0c;添加自定義表內容并插入數據 1. 新建庫表 2. 插…

STM32 輸入捕獲,串口打印,定時器,中斷綜合運用

實驗目的 使用定時器 2 通道 2 來捕獲按鍵 2 按下時間&#xff0c;并通過串口打印。 計一個數的時間&#xff1a;1us&#xff0c;PSC71&#xff0c;ARR65535 下降沿捕獲、輸入通道 2 映射在 TI2 上、不分頻、不濾波輸入捕獲原理定時器輸入捕獲實驗配置步驟測量按鍵按下時長思路…

Nacos-2--Nacos1.x版本的通信原理

在Nacos 1.x版本中&#xff0c;客戶端長輪詢&#xff08;Long Polling&#xff09;和服務端UDP主動推送是兩種不同的機制&#xff0c;分別用于配置管理和服務發現場景。它們的核心目標都是實現動態更新的實時感知&#xff0c;但實現方式、數據內容和適用場景完全不同。 1、長輪…

機器學習——09 聚類算法

1 聚類算法聚類算法&#xff1a; 是一種無監督學習算法&#xff0c;它不需要預先知道數據的類別信息&#xff0c;而是根據樣本之間的相似性&#xff0c;將樣本劃分到不同的類別中&#xff1b;不同的相似度計算方法&#xff0c;會得到不同的聚類結果&#xff0c;常用的相似度計算…

生成式AI應用生態的爆發與專業化演進:從零和博弈到正和共贏

2025年,生成式AI產業規模已突破7000億元,全球生成式AI市場規模預計在2028年達到2842億美元(IDC數據)。在這場技術革命中,AI基礎模型的分化已證明:差異化競爭而非同質化替代,才是推動產業發展的核心邏輯。如今,這一規律正從基礎模型層向應用生成平臺層蔓延——Lovable、…

Mysql——Sql的執行過程

目錄 一、Sql的執行過程流程圖解 二、Sql的執行過程流程 1.2.1、建立連接 1.2.2、服務層(緩存、解析器、預處理器、優化器、執行器) 1.2.2.1、緩存 1.2.2.2、解析器 1.2.2.3、預處理器 1.2.2.4、優化器 1.2.2.5、執行器 1.2.3、引擎層 一、Sql的執行過程流程圖解 Sql的執行過…

【Axure高保真原型】地圖路線和定位

今天和大家分享地圖路線和定位的原型模版&#xff0c;載入后&#xff0c;可以查看汽車行進路線和所在定位 提供了停靠和不停靠站點兩個案例&#xff0c;具體效果可以打開下方原型地址體驗或者點擊下方視頻觀看 【Axure高保真原型】地圖路線和定位【原型預覽含下載地址】 https…

【96頁PPT】華為IPD流程管理詳細版(附下載方式)

篇幅所限&#xff0c;本文只提供部分資料內容&#xff0c;完整資料請看下面鏈接 https://download.csdn.net/download/2501_92808811/91633108 資料解讀&#xff1a;華為IPD流程管理詳細版 詳細資料請看本解讀文章的最后內容 華為的集成產品開發&#xff08;IPD&#xff09;…

深度解析Mysql的開窗函數(易懂版)

SQL 開窗函數&#xff08;Window Function&#xff09;是一種強大的分析工具&#xff0c;它能在保留原有數據行的基礎上&#xff0c;對 "窗口"&#xff08;指定范圍的行集合&#xff09;進行聚合、排名或分析計算&#xff0c;解決了傳統GROUP BY聚合會合并行的局限性…

Java靜態代理和動態代理

Java靜態代理和動態代理 靜態代理 現在有一個計算類&#xff0c;有四個方法&#xff0c;加減乘除&#xff0c;如果需要給這四個方法都加上同一個邏輯&#xff0c;可以創建一個類作為代理類&#xff0c;把計算類注入到這個類中&#xff0c;然后再代理類中定義方法&#xff0c;并…

MySQL——MySQL引擎層BufferPool工作過程原理

目錄一、MySQL引擎層BufferPool工作過程圖解二、MySQL引擎層BufferPool工作過程原理一、MySQL引擎層BufferPool工作過程圖解 圖解 二、MySQL引擎層BufferPool工作過程原理 首先關閉自動提交&#xff0c;執行一條修改語句。 SET AUTOCOMMIT 0; update employees set name張三…

Python初學者筆記第二十二期 -- (JSON數據解析)

第31節課 JSON數據解析 1.JSON基礎概念 JSON 是一種輕量級的數據交換格式&#xff08;另一個叫XML&#xff09;&#xff0c;具有簡潔、易讀的特點&#xff0c;并且在不同編程語言之間能很好地實現數據傳遞。在 Python 中&#xff0c;json模塊能夠實現 Python 數據類型與 JSON 數…

基于多模態大模型的個性化學習路徑生成系統研究

摘要 隨著互聯網技術的迅猛發展&#xff0c;個性化學習路徑生成系統的研究在教育領域日益凸顯其重要性。本研究聚焦于基于多模態大模型的個性化學習路徑生成系統&#xff0c;旨在通過整合多模態數據&#xff0c;為學習者提供更加精準、個性化的學習路徑。多模態大模型&#xf…

ESP32 燒錄固件失敗原因排除

ESP32 燒錄固件時&#xff0c;有哪些特殊引腳需要注意電平狀態的在 ESP32 燒錄固件時&#xff0c;有幾個關鍵引腳的電平狀態會直接影響燒錄過程&#xff0c;需要特別注意&#xff1a;GPIO0&#xff08;BOOT 引腳&#xff09;&#xff1a;燒錄模式&#xff1a;需要拉低&#xff…

3D視覺系統在機器人行業中的應用

視覺引導機器人技術&#xff08;VGR&#xff09;具有成熟的2D成像技術&#xff0c;但是經濟高效的3D技術的出現使機器人應用的可能性更大。工業自動化的第一次迭代使用“盲”機器人&#xff0c;該機器人取決于待處理材料的精確定位。這樣的機器人相對不靈活&#xff0c;只能通過…

MySQL高可用改造之數據庫開發規范(大事務與數據一致性篇)

文章目錄一、前言二、延遲的原因三、大事務處理規范3.1. 刪除類操作優化設計3.2. 大事務通用拆分原則四、數據一致性核對規范4.1. 主從變更記錄識別方法五、小結一、前言 MySQL 高可用架構中最基礎、最為核心的內容&#xff1a;MySQL 復制&#xff08;Replication&#xff09;…

第9節 大模型分布式推理核心挑戰與解決方案

文章目錄 # 前言 一、通信瓶頸突破:讓數據“跑”得更快 1. 問題:通信為什么會成為瓶頸? 2. 解決方案:從硬件到算法的全鏈路優化 (1)硬件層:升級“高速公路” (2)算法層:給數據“瘦身”并“錯峰出行” (3)架構層:讓數據“少跑路” 3. 效果評估:如何判斷通信瓶頸已…

ESP32開發板接4陣腳屏幕教程(含介紹和針腳編號對應)

“4針屏幕” 一般有兩種常見類型&#xff1a;IC 屏幕&#xff08;如 0.96" OLED、SSD1306 等&#xff09; 4 個針腳通常是&#xff1a;VCC → 接 ESP32 的 3.3V&#xff08;有的屏幕支持 5V&#xff09;GND → 接 ESP32 的 GNDSCL&#xff08;時鐘&#xff09;→ 接 ESP32…