iOS事件響應鏈(Responder Chain)

来源:http://www.cnblogs.com/wang-com/archive/2017/07/27/7242729.html
-Advertisement-
Play Games

iOS響應鏈,UIResponder Chain,事件傳遞鏈,事件響應鏈 ...


  • 概述

在iOS中,視圖的層級一般都是 父視圖->添加各種子視圖。這時候某個視圖(子視圖)上有個按鈕,需要我們交互。但是有時候我們會發現無論如何都沒有反應。這時候可能就是我們對iOS的事件傳遞響應還有些迷茫。

  1. 事件的傳遞:簡單的來說就是事件的傳遞順序。他是系統向可響應的離用戶最近的視圖傳遞。大致流程就是 UIKit -> ... -> root view -> ... -> initial view 。(方式是從上到下傳遞)
  2. 事件的響應:在我們的視圖中一般都是樹狀結構,有層級關係。那麼這時候用戶點擊某個控制項,所觸發的是子視圖還是父視圖,這種有一個先後的關係,就構成了一個鏈條,我們就叫做“響應者鏈條”。響應的大致順序就是,首先查看initial view 是否能夠處理這個事件,如果不能事件上傳給其父視圖;如若上級視圖仍然不能夠處理則會繼續上傳;一直傳遞到視圖的控制器,那麼首先判斷該控制器的根視圖View是否能夠處理此事件;如果不能那麼繼續上傳(對於目前本身的視圖控制器本身還在另一個視圖控制器中,則繼續交由給其父控制器的根視圖繼續處理,如果不能那麼就要交給父控制器的控制器來處理);一直到 window,如果還是不能處理,那麼就要交給 application 處理,還是不能那麼就被丟棄。(傳遞方式是從下到上傳遞)
  • iOS中的事件
  1. 觸摸事件
  2. 加速計事件
  3. 遠程式控制制事件
  • 觸摸事件

  響應者對象(UIResponder)

  在iOS中,只要是繼承UIResponder的對象都可以接收並處理事件。在iOS中提供了一些方法來處理觸摸事件。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;      // 開始觸摸View時會調用一次
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;      // 隨著手指一動會多次調用
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;      // 手指離開的時候會調用
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;    // 觸摸結束前,電話打進來,會自動調用這個方法

   事件的產生

  當發生一個觸摸事件後,系統會將觸摸事件添加到UIApplication管理的事件隊列中(先進先出) ->  UIApplication 從事件隊列中拿出最前的事件將之分發出去,通常是首先發送事件給應用程式的主視窗  ->  主視窗會找到一個最合適的視圖來處理觸摸事件  ->  找到合適的視圖控制項後,就會調用控制項的上述方法中的一個或者多個來處理具體的事件處理。

  事件的傳遞

  主視窗先判斷能不能接收這個觸摸事件,如若不能,就直接return;

  主視窗可以接收,傳遞給子視圖,繼續判斷,繼續傳遞,迴圈直到沒有能夠符合響應的子控制項,那麼這時候的就會認為由自己來處理這個事件最合適。

  也有不能響應的情況:

    1.  不允許交互

    2.  控制項隱藏

    3.  透明度過低(<0.01)

  如何尋找最適合的控制項來處理事件

  UIView 及其子類有兩個非常重要的方法

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   // default returns YES if point is in bounds

  當只要有事件傳遞給這個控制項,這個控制項就會調用 

hitTest: withEvent:

  其作用是尋找並返回最合適的View,不管這個控制項能不能處理事件,也不管觸摸點是不是在這個空間上,都會先接收事件,然後調用方法。

  所以這裡我們就有了可操作空間 , 因為不管點擊事件發生在哪裡,最終能夠處理事件的View都是這個方法返回的View。通過重寫這個方法我們可以攔截整個事件的傳遞過程,同時可以指定處理事件的View。(如果這個方法返回的是nil,那麼調用該方法的控制項本身以及其子控制項均不能處理事件,只能由其父視圖來處理事件)

  所以事件的傳遞順序 :產生觸摸事件 -> UIApplication事件隊列 -> [UIWindow hitTest:withEvent:] -> 返回更合適的View -> [子控制項 hitTest:withEvent:] -> 返回最合適的View ...

  所以這裡我們可以得到的結論就是:不管子控制項是不是最合適的View,都會調用 hitTest 方法,如果不是最合適的View,會返回nil,同時認定其父視圖是最合適的View。

  小技巧:在父控制項中返回最合適的子控制項。因為如果在自己返回自己,有可能兩個視圖 B,C 同時載入 A 上,當設置B為最合適的View,這時候如果我們在 B 中返回自己,可能我們點擊到 C 這時候 B 還沒來及返回系統就已經定位到了 C 。

  尋找最合適的View底層剖析

// 什麼時候調用:只要事件一傳遞給一個控制項,那麼這個控制項就會調用自己的這個方法
// 作用:尋找並返回最合適的view
// UIApplication -> [UIWindow hitTest:withEvent:]尋找最合適的view告訴系統
// point:當前手指觸摸的點
// point:是方法調用者坐標繫上的點
- (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) {
            // 如果能找到最合適的view
            return fitView;
        }
    }
    // 4.沒有找到更合適的view,也就是沒有比自己更合適的view
    return self;
}

  通過重寫 View 的 hitTest 方法,即可找到最合適的 View

 

  另一個比較重要的方法

pointInside: withEvent: 

  方法是用來判斷我們觸摸事件的點位置是否在當前View上,如果返回 NO 說明是不在當前 View 坐標繫上,同時自然是不能夠處理事件的。

 

 

  事件的響應

  傳遞方式是 從下往上 的傳遞方式。

  

  事件處理流程

  產生觸摸事件 -> 事件添加到 UIApplication 隊列中 -> 事件傳遞主視窗 -> 找到最合適的View -> 最合適的View調用自己的touch方法來處理事件 -> touches預設做法是把事件順著響應鏈往上傳遞

//只要點擊控制項,就會調用touchBegin,如果沒有重寫這個方法,自己處理不了觸摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    // 預設會把事件傳遞給上一個響應者,上一個響應者是父控制項,交給父控制項處理
    [super touchesBegan:touches withEvent:event];
    // 註意不是調用父控制項的touches方法,而是調用父類的touches方法
    // super是父類 superview是父控制項
}

  當我們需要做到一個事件多個對象同時處理的話,我們就可以先處理自己的事件之後,調用 super 方法。

 

  • 實例應用

  當我們要擴大按鈕點擊範圍

  比如我們有一個 20pt*20pt 的 按鈕,我們可以在一個控制項的中利用 hitTest 來實現。 例如一個 UIButton,自定義一個按鈕,在其自定義類中重寫方法。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1.判斷下視窗能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil;

    // 擴大到按鈕之外的都是點擊範圍
    CGRect touchRect    = CGRectInset(self.bounds, -20, -20);
    if (CGRectContainsPoint(touchRect, point)) {
        for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint  = [subView convertPoint:point toView:self];
            UIView *hitTestView     = [subView hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

  將事件傳遞給兄弟View(A 與 B是同一個父視圖,但是 B 有部分遮擋住了 A ;點擊遮擋部分需要 A 響應事件)這時候點擊 A 是不會有任何響應的,除非 B 的userInteractionEnable 為 NO , 但是我們用 hitTest 同樣可以做到,重寫 B 的這個方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitTestView = [super hitTest:point withEvent:event];
    if (hitTestView == self) {
        hitTestView = nil;
    }
    return hitTestView;
}

 

 

  

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今早,上IT修真園裡,看到師兄大娃很負責任的將我任務里的項目的排版,3,6,7的列了出來。 謝謝師兄,那麼負責任的照看師弟。 言歸正傳,我一開始,直接按照師兄的指示,選擇性的優先修改底部。效果也達到了預期的效果。後來我為了查看我的項目跟psd圖的差別。就直接上我們的IT修真園的首頁,查看它的代碼了。 ...
  • 1. 如何創建嵌套的過濾器 //允許你減少集合中的匹配元素的過濾器, //只剩下那些與給定的選擇器匹配的部分。在這種情況下, //查詢刪除了任何沒(:not)有(:has) //包含class為“selected”(.selected)的子節點。 .filter(":not(:has(.select ...
  • Atwood’s Law是Jeff Atwood在2007年提出的:“any application that can be written in JavaScript, will eventually be written in JavaScript.”。據說,這隻是當時開的一個玩笑。不過,這個玩 ...
  • 使用Android DataBinding簡化Adapter的開發 ...
  • 它是如何觸發的? 這個值的狀態是燒錄在主板上,無法刷寫修改, 從0到1 不可逆,除非替換硬體 If a non-Knox boot loader or kernel has been installed on the device, Knox can no longer guarantee the ...
  • 一,效果圖。 二,工程圖。 三,代碼。 ViewController.h ViewController.m ...
  • Android Studio是官方推出的Android開發IDE,本系列講解Android Studio中常用的快捷鍵,本文是該系列的第一篇,講解的內容是與編輯代碼相關的快捷鍵。 本文所講快捷鍵基於Android Studio2.3.3 windows版本。 本文所記錄的快捷鍵皆親自實踐,全部可用。 ...
  • Mac PlantUML 安裝教程: http://blog.csdn.net/linuxcjh/article/details/51105294 為了簡化使用,可以在 Sublime 里配置個快捷鍵。打開 Preferences -> Key Binding - User,添加一個快捷鍵: { " ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...