響應者鏈

一. Hit-Testing

  1. 什么是Hit-Testing?

    • 對于觸摸事件, window首先會嘗試將事件交給事件觸發點所在的View來處理, 也就是hit-test view
    • 而尋找hit-test view的這個過程, 被稱之為hit-testing
    • Hit-Testing Returns the View Where a Touch Occurred, 這個過程會返回點擊事件所在的View
  2. Hit-Testing的過程

    • 系統首先找到觸摸點所在位置的View, 然后找到這個View的最高級父控件
    • 從最高級的父控件開始進行尋找, 首先會調用hit-test方法
    • hit-test內部調用pointInside方法, 來判斷觸摸點point是否在當前的View中
      • 如果在, 則返回YES, 則進入這個View中, 繼續遍歷他的子控件, 執行hit-test方法
      • 如果不在, 則返回NO, 離開這個View的hit-test檢查, 執行同級另一個View的hit-test繼續查找
    • 遍歷到在View等級系統中, 處于最低級的View, 他沒有子控件, 因此這個就是最合適的hit-test view, 點擊事件就會交給他來處理
    • 遞歸: 進入hit-test -> 調用pointInside -> (如果返回YES, 則進入子控件的hit-test -> 回到第一步) -> 離開hit-test
  3. Hit-tset的一般作用

    • 阻止某個View的響應(應用的較少), 讓pointInside:方法始終返回NO, 即可阻止這個View的響應

    • 擴大按鈕的響應區域(如果按鈕過小, 但是你想擴大他的響應范圍)

        - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
            return CGRectContainsPoint(HitTestingBounds(self.bounds, self.minimumHitTestWidth, self.minimumHitTestHeight), point);
        }   
                
        CGRect HitTestingBounds(CGRect bounds, CGFloat minimumHitTestWidth, CGFloat minimumHitTestHeight) {
            CGRect hitTestingBounds = bounds;
            
            // 如果要求的寬度, 超過了控件的寬度
            if (minimumHitTestWidth > bounds.size.width) {
                hitTestingBounds.size.width = minimumHitTestWidth; // 修改響應的寬度
                // 響應的X值 -= (響應的寬度 - 原寬度) / 2  (X)
                hitTestingBounds.origin.x -= (hitTestingBounds.size.width - bounds.size.width)/2;
            }
            if (minimumHitTestHeight > bounds.size.height) {
                hitTestingBounds.size.height = minimumHitTestHeight;
                hitTestingBounds.origin.y -= (hitTestingBounds.size.height - bounds.size.height)/2;
            }
            return hitTestingBounds;
        }
      
    • 子控件超出父控件后無法響應(TabBar按鈕超出父控件范圍)

        - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        
            if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
                return nil;
            }
            /**
             *  此注釋掉的方法用來判斷點擊是否在父View Bounds內,
             *  如果不在父view內,就會直接不會去其子View中尋找HitTestView,return 返回
             */
        //    if ([self pointInside:point withEvent:event]) {
                for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
                    CGPoint convertedPoint = [subview convertPoint:point fromView:self];
                    UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event]; // 尋找該控件中的hit-test view
                    if (hitTestView) {
                        return hitTestView;
                    }
                }
                return self;
        //    }
            return nil;
        }
      

二. 響應者鏈條由響應者組成(The Responder Chain Is Made Up of Responder Objects)

  1. hit-test view是最優先處理事件的View, 如果hit-test view不能處理事件的話, 那么事件會沿著響應者鏈條找到能夠處理這個事件的View.

  2. 響應者鏈條的事件傳遞:

    • 響應者鏈條是由一系列的響應者對象構成的, 很多事件都是依靠響應者鏈條來實現事件傳遞
    • 傳遞從first responder開始, 到Application結束
    • 如果first responder不能處理這個事件, 那么系統會順著響應者鏈條向前尋找下一個響應者
  3. 響應者對象(Responder object):

    • 響應者對象能夠響應處理事件, UIResponder類是所有響應者對象的基類, 他定義了事件處理以及一般響應行為
    • 凡是繼承自UIResponder, 例如UIView,UIApplication,UIViewController等, 都可以響應事件.
    • 注意: CoreAnimation Layers不是響應者
  4. 第一響應者(First Responder):

    • First Responder用于第一個來接收事件.
    • 一般First Responder都是一個View對象, 一個對象變為First Responder需要通過一下兩種情況:
      • 重寫canBecomeFirstResponder方法, 并且返回YES
      • 接收becomeFirstResponder消息, 響應者對象可以給自己發送這個消息
  5. 要在View完全渲染結束的時候, 才能指定其成為First Responder:

    • 例如: 如果在ViewWillAppear方法中調用becomeFirstResponder方法, 那么這個方法總是會返回NO
    • 因此, 應該在viewDidAppear方法中, 指定稱為First Responder
  6. 除了Event事件, 還有其他幾種情況需要使用到響應者鏈:

    1. 觸摸事件(TouchEvent): 如果hitTest view無法處理一個觸摸時間, 那么這個事件就會放棄這個hit-test view的響應者鏈
    2. 運動事件(MotionEvent): 如果要處理shake-motion搖動事件, 那么當前First responder對象必須要實現motionBegan:withEvent:motionEnded:withEvent:兩個方法之一
    3. 遠程控制事件(RemoteControlEvent): 處理這類時間, 當前的First responder必須實現remoteControlReceivedWithEvent方法
    4. 行動消息(ActionMessage): 當用戶點擊一個UIButton或UISwitch等控件的時候, 如果消息的發出者target為nil的話, 那么這條消息會由First Responder的響應鏈的開始發出
    5. 編輯菜單消息(文本的賦值粘貼)(Editing-menu messages): 當點擊編輯菜單的命令時, iOS會通過響應者鏈條尋找實現了cut: copy paste:方法的對象
    6. 文本編輯(TextEditing): 當用戶使用UITextField或UITextView時, 這個View會自動的變為First Responder, 通常情況下, 鍵盤會自動的彈出并且TextView會進入編輯狀態. 你可以展示一個自定義的輸入View來替代鍵盤, 同時可以給任意一個響應者對象添加一個自定義的輸入View.
  7. 只有TextView和TextField才會自動變成First Responder, 其他的響應者對象, 需要主動調用becomeFirstResponder方法來稱為第一響應者


The Responder Chain Is Made Up of Responder Objects(響應者鏈條是由響應者對象構成的)

Many types of events rely on(依靠) a responder chain for event delivery(事件傳遞). The responder chain is a series of linked responder objects(一系列相連的響應者對象). It starts with the first responder and ends with the application object(從第一響應者開始, 到Application結束). If the first responder cannot handle an event, it forwards the event to the next responder in the responder chain.(如果第一響應者無法處理, 則順著鏈條向前找下一個響應者)

A responder object is an object that can respond to and handle events(響應者對象能響應和處理時間). The UIResponder class is the base class for all responder objects, and it defines the programmatic interface not only for event handling but also for common responder behavior(UIResponder類定義了事件處理以及一般響應行為). Instances of the UIApplication, UIViewController, and UIView classes are responders, which means that all views and most key controller objects are responders. Note that Core Animation layers are not responders(CALayer不是響應者).

The first responder is designated to receive events first(First responder用于第一個接收事件). Typically, the first responder is a view object. An object becomes the first responder by doing two things(一個對象變成First responder通過兩件事):

  1. Overriding the canBecomeFirstResponder method to return YES重寫canBecomeFirstResponder方法, 并返回YES.
  2. Receiving a becomeFirstResponder message. If necessary, an object can send itself this message接收becomeFirstResponder消息, 一個對象可以給自己發這個消息.

Note: Make sure that your app has established its object graph(建立他的對象圖表) before assigning an object to be the first responder(在指定一個對象變為first responder之前). For example, you typically call the becomeFirstResponder method in an override of the viewDidAppear: method. If you try to assign the first responder in viewWillAppear:, your object graph is not yet established, so the becomeFirstResponder method returns NO如果在你的View沒有渲染完畢時讓他成為第一響應者, 這時候這個方法始終會返回NO.

Events are not the only objects that rely on the responder chain(事件不只是唯一的依靠于響應者鏈條的). The responder chain is used in all of the following:

  1. Touch events(觸摸事件). If the hit-test view cannot handle a touch event, the event is passed up錯過 a chain of responders響應者鏈條 that starts with the hit-test view從hit-test view開始的.

  2. Motion events(運動事件). To handle shake-motion(搖動事件) events with UIKit, the first responder must implement either the motionBegan:withEvent: or motionEnded:withEvent: method of the UIResponder class, as described in Detecting Shake-Motion Events with UIEvent第一響應者需要實現motionBeganmotionEnded兩個方法之一.

  3. Remote control events(遠程控制事件). To handle remote control events, the first responder must implement the remoteControlReceivedWithEvent: method of the UIResponder class.遠程控制事件, 必須實現remoteControlReceivedWithEvent方法

  4. Action messages(行動消息(按鈕)). When the user manipulates(操控) a control, such as a button or switch, and the target(目標為nil) for the action method is nil, the message is sent through a chain of responders starting with the first responder, which can be the control view itself(當action message被處罰, 并且沒有方法對應的target的時候, 這個message由first responder的響應者鏈條的開始發出).

  5. Editing-menu messages(編輯菜單消息(文本的賦值粘貼)). When a user taps the commands of the editing menu, iOS uses a responder chain to find an object that implements the necessary methods (such as cut:, copy:, and paste:)(當點擊編輯菜單的命令時, iOS會通過響應者鏈條尋找實現了cut: copy paste:方法的對象). For more information, see Displaying and Managing the Edit Menu and the sample code project, CopyPasteTile.

  6. Text editing(文本編輯). When a user taps a text field or a text view(TextField和TextView), that view automatically becomes the first responder(這個View會自動的變成first responder). By default, the virtual keyboard appears and the text field or text view becomes the focus of editing(鍵盤彈出, textView進入編輯). You can display a custom input view instead of the keyboard(你可以顯示一個自定義的輸入View, 而不是鍵盤) if it’s appropriate for your app. You can also add a custom input view to any responder object(你可以給任意一個響應者對象添加input view). For more information, see Custom Views for Data Input.

UIKit automatically sets the text field or text view that a user taps to be the first responder(UIKit 自動的將textView和textField設置為first responder當開始編輯的時候); Apps must explicitly set all other first responder objects with the becomeFirstResponder method(其他的響應者對象, 必須手動調用becomeFirstResponder方法).


三. 響應者鏈的傳遞方式(The Responder Chain Follows a Specific Delivery Path)

  1. 響應者鏈的傳遞方式:
    • 首先, 當觸發一個事件的時候, 被點擊的View是初始View, 他會先從這個view開始尋找事件處理者
    • 如果這個View無法處理這個事件, 那么會順著他的父級(響應者鏈)繼續尋找下一個響應者
    • 如果有響應者能夠處理這個事件, 或沒有找到能夠處理這個事件的響應者, 則傳遞結束
    • 傳遞的順序: view -> ViewController -> window -> Application -> 丟棄

If the initial object(初始對象)—either the hit-test view or the first responder—doesn’t handle an event, UIKit passes the event to the next responder in the chain(如果hit-test view和first responder都無法處理事件, 那么UIKit會沿著響應者連尋找下一個響應者). Each responder decides whether it wants to handle the event or pass it along to its own next responder by calling the nextResponder method(每個響應者都可以決定是否要去處理時事件或者通過調用nextResponder方法, 交個下一個響應者處理).This process continues until a responder object either handles the event or there are no more responders(當有響應者處理這個事件, 或沒有更多響應者的時候結束傳遞).

The responder chain sequence(鏈條) begins when iOS detects(檢測到) an event and passes it to an initial object, which is typically a view. The initial view has the first opportunity to handle an event. Figure 2-2 shows two different event delivery paths for two app configurations. An app’s event delivery path depends on its specific construction(當前的構造), but all event delivery paths adhere to(依附) the same heuristics(探索法).

Figure 2-2 The responder chain on iOS

For the app on the left, the event follows this path:

  1. The initial view attempts to handle the event or message. If it can’t handle the event, it passes the event to its superview, because the initial view is not the top most view in its view controller’s view hierarchy(如果接收到事件的初始View無法處理事件, 那么這個事件會交給他的SuperView, 因為他不是viewController等級中的最高級View).

  2. The superview attempts to handle the event. If the superview can’t handle the event, it passes the event to its superview, because it is still not the top most view in the view hierarchy.

  3. The topmost view in the view controller’s view hierarchy attempts to handle the event. If the topmost view can’t handle the event, it passes the event to its view controller.

  4. The view controller attempts to handle the event, and if it can’t, passes the event to the window.

  5. If the window object can’t handle the event, it passes the event to the singleton app object.

  6. If the app object can’t handle the event, it discards(丟棄) the event.

  7. view -> ViewController -> window -> Application -> 丟棄

The app on the right follows a slightly different path, but all event delivery paths follow these heuristics:

  1. A view passes an event up its view controller’s view hierarchy until it reaches the topmost view.

  2. The topmost view passes the event to its view controller.

  3. The view controller passes the event to its topmost view’s superview.
    Steps 1-3 repeat until the event reaches the root view controller.

  4. The root view controller passes the event to the window object.

  5. The window passes the event to the app object.

Important: If you implement a custom view to handle remote control events, action messages, shake-motion events with UIKit, or editing-menu messages, don’t forward the event or message to nextResponder directly to send it up the responder chain(不要手動直接調用nextResponder方法, 將事件直接傳遞給下一個響應者). Instead, invoke the superclass implementation of the current event handling method and let UIKit handle the traversal of the responder chain for you(應該調用父類的時間處理方法的實現, 并且讓UIKit通過響應者鏈來傳送事件).


四. 總結

  1. 響應者鏈是由當前你所點擊的view的父級結構 + window + Application組成
  2. 事件的響應分為兩個階段, hit-test尋找響應者
    • hit-test
      • 當用戶觸摸屏幕的時候, 系統會從最高級, 也就是RootView開始調用hitTestpointInside方法, 來尋找最適合處理事件的View, 即Hit-test View
      • 這個過程總是從View等級中最高的View開始, 不斷的遍歷Subview
      • 可以用這個方法來手動選擇負責響應事件的View
    • Fine Responder
      • 當hit-test尋找到hit-test View之后, 會從這個view開始判斷能否處理事件
      • 如果不能, 系統會自動通過響應者鏈來尋找下一個響應者繼續判斷
      • 如果找到了能夠響應事件的響應者, 那么這個事件由這個響應者來處理
      • 如果沒有響應者能夠接收直到Application也無法處理, 那么這個事件將會被丟棄
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容