事件傳遞鏈和響應鏈深度分析

  1. 觸摸對象的產(chǎn)生晦譬。
    當iOS系統(tǒng)檢測到手指觸摸屏幕時,會將其打包成一個UIEvent對象互广,該對象包含一些處理事件所需要的信息敛腌。然后將其放入事件隊列(Event Queue)。程序的單例UIApplication對象會從隊列頭部取出一個事件對象惫皱,將其分發(fā)出去像樊。通常首先是將事件分發(fā)給程序的主窗口(keyWindow)對象,如下圖:


    timg.jpeg

    5733452-5be524aada03c255.png.jpeg
  • IOKit.framework 為系統(tǒng)內核的庫
  • SpringBoard.app 相當于手機的桌面
  • Source1 主要接收系統(tǒng)的消息
  • Source0 - UIApplication - UIWindow

UIEvent
An object that describes a single user interaction with your app.

描述單次用戶與應用的交互行為旅敷。包括觸摸事件生棍、運動事件、遠程控制事件和按壓事件四種類型媳谁,一般討論的是觸摸事件涂滴。


022058515011011.png

觸摸事件對象包含與事件相關的觸摸(手指接觸屏幕)友酱。觸摸事件對象可以包含一個或多個觸摸,并且每個觸摸都由UITouch對象表示氢妈。
多點觸控時粹污,UIKit會重用相同的UIEvent對象,因此不建議在app中強引用UIEvent或UITouch首量,而是將相關數(shù)據(jù)(比如坐標)從其中復制出來壮吩。
下面是UIEvent的屬性和方法:

/// 事件類型
@property(nonatomic,readonly) UIEventType    type;
/// 事件子類型,一般用于遠程控制事件
@property(nonatomic,readonly) UIEventSubtype  subtype;
/// 事件觸發(fā)時間
@property(nonatomic,readonly) NSTimeInterval  timestamp;
/// 事件包含的touch對象加缘,不同touch可能來源于不同view或window
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
/// 返回來源于指定window的touch對象
- (nullable NSSet *)touchesForWindow:(UIWindow*)window;
/// 返回來源于指定view的touch對象
- (nullable NSSet *)touchesForView:(UIView*)view;
/// 返回來源于指定手勢的touch對象
- (nullable NSSet *)touchesForGestureRecognizer:(UIGestureRecognizer*)gesture;
/// 回與指定主要觸摸相關聯(lián)的由于在屏幕上滑動太快而丟失的需要合并的所有觸摸鸭叙。因為有些屏幕幀速可能會比較低,當你使用一款繪圖軟件時快速畫一個圓拣宏,那么呈現(xiàn)出來的可能會是一個看起來像是不規(guī)則多邊形的東西沈贝,這就是因為丟失了一些輔助觸摸事件的原因,這一屬性可以提高觸摸精度勋乾。
- (nullable NSArray *)coalescedTouchesForTouch:(UITouch*)touchAPI_AVAILABLE(ios(9.0));
/// 返回的觸摸表示系統(tǒng)根據(jù)用戶過去的輸入估計用戶觸摸輸入的位置宋下。處理用戶的觸摸輸入并將該信息轉換為繪圖命令需要時間,而將這些繪圖命令轉換為渲染內容則需要額外的時間辑莫。此方法的預測觸摸最小化感知的延遲学歧,常用會繪圖等應用程序。
- (nullable NSArray *)predictedTouchesForTouch:(UITouch*)touchAPI_AVAILABLE(ios(9.0));

UITouch
An object representing the location, size, movement, and force of a touch occurring on the screen.
表示在屏幕上發(fā)生的觸摸的位置各吨、大小枝笨、移動和力度的對象。
當手指移動時揭蜒,系統(tǒng)會更新同一個UITouch對象横浑,使之能夠一直保存該手指在的觸摸位置。當手指離開屏幕時屉更,系統(tǒng)會銷毀相應的UITouch對象徙融。
下面是UITouch的屬性和方法:

/// 觸發(fā)時間
@property(nonatomic,readonly) NSTimeInterval      timestamp;
/// 狀態(tài),包括手指開始瑰谜,手指移動张咳,手指靜止,手指離開(結束)似舵,意外中斷(取消)
@property(nonatomic,readonly) UITouchPhase        phase;
/// 短時間內單擊的次數(shù)
@property(nonatomic,readonly) NSUInteger          tapCount;
/// 觸摸類型,包括手指直接接觸葱峡,間接接觸觸摸砚哗,觸筆觸摸
@property(nonatomic,readonly) UITouchType         type;
/// 半徑
@property(nonatomic,readonly) CGFloat majorRadius;
/// 半徑誤差
@property(nonatomic,readonly) CGFloat majorRadiusTolerance;
/// 所在window
@property(nullable,nonatomic,readonly,strong) UIWindow                        *window;
/// 所在view
@property(nullable,nonatomic,readonly,strong) UIView                          *view;
/// 接收到觸摸的手勢
@property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers ;
/// 當前位置在指定view坐標系的坐標
- (CGPoint)locationInView:(nullable UIView *)view;
/// 前一個位置在指定view坐標系的坐標
- (CGPoint)previousLocationInView:(nullable UIView *)view;

/// 返回精確的當前位置,不要在hit test中使用
- (CGPoint)preciseLocationInView:(nullable UIView *)view;
/// 返回精確的前一個位置砰奕,不要在hit test中使用
- (CGPoint)precisePreviousLocationInView:(nullable UIView *)view;

/// 按壓力度
@property(nonatomic,readonly) CGFloat force;
/// 最大可能的力度
@property(nonatomic,readonly) CGFloat maximumPossibleForce;
/// 方位角度只用于觸控筆
- (CGFloat)azimuthAngleInView:(nullable UIView *)view;
/// 指向方位角方向的單位矢量蛛芥。僅對觸控筆類型有效提鸟。
- (CGVector)azimuthUnitVectorInView:(nullable UIView *)view;
/// 高度角。僅對觸控筆類型有效仅淑。
@property(nonatomic,readonly) CGFloat altitudeAngle;
/// 此屬性包含當前觸摸數(shù)據(jù)的唯一標記称勋,當每個觸摸對象的觸摸特性發(fā)生變化時,該值將會單獨增加,返回值是NSNumber 索引號涯竟,關聯(lián)更新的觸摸與原始觸摸赡鲜。
@property(nonatomic,readonly) NSNumber * _Nullable estimationUpdateIndex;
/// 當前觸摸對象估計的觸摸屬性,包括力度庐船,方位银酬,高度,位置
@property(nonatomic,readonly) UITouchProperties estimatedProperties;
/// 一組期望將來有傳入更新的屬性筐钟。如果估計屬性沒有更新揩瞪,則當前值是我們的最終估計值
@property(nonatomic,readonly) UITouchProperties estimatedPropertiesExpectingUpdates;
  1. 事件傳遞鏈,確定事件的第一響應者(hit-testing)篓冲。
    對于觸摸事件來講李破,window對象會首先嘗試將事件分發(fā)給觸摸事件發(fā)生的那個視圖上。這一視圖通常被稱為hit-test視圖壹将,而查找這一視圖的過程就叫做hit-testing嗤攻。
    UIKit使用基于視圖的hit-testing來確定Touch事件在哪里產(chǎn)生。UIKit將Touch位置與視圖層級中的視圖對象的邊界進行了比較瞭恰。UIView的hitTest:withEvent:方法在視圖層級中執(zhí)行屯曹,尋找最深的包含指定Touch的子視圖,這個視圖將成為Touch事件的第一響應者惊畏。
    UIKit不變的分配每一個Touch給包含它的視圖恶耽。UIKit創(chuàng)建UITouch對象當touch第一次產(chǎn)生時,釋放這個UITouch對象在touch結束時颜启。當touch位置或者其他參數(shù)改變時偷俭,UIKit更新UITouch對象新的信息。只有包含它的視圖這個屬性不會改變缰盏。甚至這個touch位置移動到初始視圖的外面涌萤,這個屬性也不會改變。
    這個方法忽略以下情況:

視圖是隱藏的 hidden = YES
用戶交互關閉的 userInteractionEnabled = NO
透明度小于0.01的 alpha < 0.01
點在接收者的范圍之外不會被命中口猜,即使它們實際上處于接收者的子視圖之內负溪。如果當前視圖的cilpsToBounds屬性被設置為NO,影響了子視圖超過當前視圖會產(chǎn)生這種情況。

怎么尋找最合適的view济炎?
這里要用到兩個方法川抡。

/// 此方法返回的View是本次點擊事件需要的最佳View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

/// 判斷一個點是否落在范圍內
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
1500535769427341.png

hitTest:withEvent:工作流程:
首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內。
若不在须尚,說明觸摸點不在當前視圖內崖堤,則hitTest:withEvent:返回nil侍咱。
若在,說明觸摸點在當前視圖內密幔,則遍歷當前視圖的所有子視圖(subviews)楔脯,調用子視圖的hitTest:withEvent:方法重復前面的步驟,子視圖的遍歷順序是從top到bottom胯甩,即從subviews數(shù)組的末尾向前遍歷昧廷,直到有子視圖的hitTest:withEvent:方法返回非空對象或者全部子視圖遍歷完畢,返回非空時會終止子視圖的遍歷蜡豹。直到找到最終的視圖沒有subviews麸粮,這就是第一響應者視圖。

系統(tǒng)默認的hitTest和pointInside處理類似如下代碼:

// 因為所有的視圖類都是繼承BaseView
- (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.從后往前遍歷自己的子控件
   NSInteger count = self.subviews.count;
   for (NSInteger 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;
       }
   }
   // 循環(huán)結束,表示沒有比自己更合適的view
   return self;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
    CGRect bounds = self.bounds;
   // CGRectContainsPoint  判斷點是否在矩形內
    return CGRectContainsPoint(bounds, point);
}

備注:可以通過重寫hitTest:withEvent: 常用于改變傳遞鏈镜廉,也可以通過重寫pointInside:withEvent:常用于改變視圖的觸摸熱區(qū)弄诲。

  1. 響應鏈。
    經(jīng)過以上的事件的傳遞過程娇唯,事件已經(jīng)傳遞給系統(tǒng)認為最適合的View了齐遵。接下來就是處理這個事件。
    處理事件方法:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
}

響應鏈是通過 nextResponder 屬性組成的一個鏈表塔插,如下圖:


5733452-f7250c9f7e9ed223.png

默認UIView會將事件上拋梗摇,其它大部分響應者不會上拋。

UIResponder
響應和處理事件的抽象類想许。
包括觸摸伶授,運動,遠程控制流纹,按壓事件糜烹。可以通過重寫特定的方法處理相應事件漱凝。例如疮蹦,要處理觸摸事件,響應程序實現(xiàn)touchesStarted:withEvent:茸炒、touchesMoved:withEvent:愕乎、touchesend:withEvent:、touchesCancelled:withEvent:方法壁公。
可以通過inputView接受自定義輸入感论。系統(tǒng)鍵盤是輸入視圖最明顯的例子。當用戶在屏幕上點擊UITextField和UITextView對象時紊册,該視圖將成為第一個響應程序并顯示其輸入視圖比肄,即系統(tǒng)鍵盤。類似地,您可以創(chuàng)建自定義輸入視圖薪前,并在其他響應程序激活時顯示它們。若要將自定義輸入視圖與響應程序關聯(lián)关斜,請將該視圖分配給響應程序的inputView屬性示括。

// 當對象成為第一個響應者時調用并顯示。上升到響應鏈痢畜。
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputView;
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputAccessoryView;

問題:
當手指觸摸從某個視圖A中拖動到視圖A外側垛膝,為什么A仍然能接收到touchesMoved消息?
解答:
當手指觸摸屏幕時丁稀,產(chǎn)生event X吼拥,并會以觸摸開始點進行hit-testing判斷,找到第一響應者线衫。單個手指的觸摸只會產(chǎn)生一個touch對象凿可,此后,滑動手指都是在更新event X中touch的坐標授账。當滑動離開視圖A時枯跑,event X的第一響應者仍然是View A。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末白热,一起剝皮案震驚了整個濱河市敛助,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屋确,老刑警劉巖纳击,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異攻臀,居然都是意外死亡焕数,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門茵烈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來百匆,“玉大人,你說我怎么就攤上這事呜投〖有伲” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵仑荐,是天一觀的道長雕拼。 經(jīng)常有香客問我,道長粘招,這世上最難降的妖魔是什么啥寇? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上辑甜,老公的妹妹穿的比我還像新娘衰絮。我一直安慰自己,他們只是感情好磷醋,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布猫牡。 她就那樣靜靜地躺著,像睡著了一般邓线。 火紅的嫁衣襯著肌膚如雪淌友。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天骇陈,我揣著相機與錄音震庭,去河邊找鬼。 笑死你雌,一個胖子當著我的面吹牛器联,可吹牛的內容都是我干的。 我是一名探鬼主播匪蝙,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼主籍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逛球?” 一聲冷哼從身側響起千元,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颤绕,沒想到半個月后幸海,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡奥务,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年物独,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氯葬。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡挡篓,死狀恐怖,靈堂內的尸體忽然破棺而出帚称,到底是詐尸還是另有隱情官研,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布闯睹,位于F島的核電站戏羽,受9級特大地震影響,放射性物質發(fā)生泄漏楼吃。R本人自食惡果不足惜始花,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一妄讯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酷宵,春花似錦亥贸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溜族,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垦沉,已是汗流浹背煌抒。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留厕倍,地道東北人寡壮。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像讹弯,于是被迫代替她去往敵國和親况既。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容