響應(yīng)者鏈工作原理
應(yīng)用程序使用Responder
對(duì)象接收和處理時(shí)間,響應(yīng)者對(duì)象是UIResponder
類的任何實(shí)例,常見的子類包括UIView
,UIViewController
和UIApplication
.響應(yīng)者收到原始事件數(shù)據(jù),并且必須處理該事件或?qū)⑵滢D(zhuǎn)發(fā)給另一個(gè)響應(yīng)者對(duì)象,當(dāng)您的應(yīng)程序收到一個(gè)事件時(shí),UIKit
會(huì)自動(dòng)將該事件指向最合適的響應(yīng)者對(duì)象,稱為第一響應(yīng)者,未處理的事件從響應(yīng)者傳遞到活動(dòng)響應(yīng)器中的響應(yīng)者,這是應(yīng)用程序的響應(yīng)者對(duì)象的動(dòng)態(tài)配置,應(yīng)用程序中沒有單個(gè)響應(yīng)者鏈,UIKit
定義了將對(duì)象從一個(gè)響應(yīng)者傳遞到下一個(gè)響應(yīng)器的默認(rèn)規(guī)則,但是您可以隨時(shí)通過覆蓋響應(yīng)者對(duì)象中相對(duì)應(yīng)的屬性來(lái)更改這些規(guī)則.下圖顯示了應(yīng)用程序的默認(rèn)響應(yīng)者鏈,其界面包含Label
,textField
,button
和兩個(gè)背景視圖,如果label不處理事件,UIKit
會(huì)將事件發(fā)送到其superView
對(duì)象,然后是窗口的根視圖,從根視圖響應(yīng)者鏈轉(zhuǎn)移到擁有該視圖的控制器,然后將事件傳遞給window,如果window
不處理,UIKit
將事件傳遞給UIApplication
對(duì)象,并且可能將該事件傳遞給AppDelegate
,
對(duì)于每種類型的事件,UIKit
指定一個(gè)第一響應(yīng)者,并首先發(fā)送事件到這個(gè)對(duì)象,第一響應(yīng)者根據(jù)事件的類型而有所不同.
- Touch events
第一響應(yīng)者是發(fā)生觸摸的視圖 - Press events
第一響應(yīng)者是有焦點(diǎn)的響應(yīng)者 - Shake-motion events
第一響應(yīng)者是(UIKit
)指定為第一個(gè)響應(yīng)者的對(duì)象 - Remote-control events
第一響應(yīng)者是(UIKit
)指定為第一個(gè)響應(yīng)者的對(duì)象 - Editing menu messages
第一響應(yīng)者是(UIKit
)指定為第一個(gè)響應(yīng)者的對(duì)象
與加速器,陀螺儀和磁力計(jì)相關(guān)的運(yùn)動(dòng)事件不遵循響應(yīng)者連,Core Motion
將這些事件直接傳遞給你指定的對(duì)象,(https://developer.apple.com/library/content/documentation/Miscellaneous/Conceptual/iPhoneOSTechOverview/CoreServicesLayer/CoreServicesLayer.html#//apple_ref/doc/uid/TP40007898-CH10-SW27)
當(dāng)手指觸摸到屏幕中,觸摸到某一個(gè)控件到響應(yīng)這個(gè)事件分為兩步:事件的傳遞與分發(fā),和事件的響應(yīng)
事件的傳遞涉及到UIView
中的兩個(gè)方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判斷當(dāng)前點(diǎn)擊事件是否存在最優(yōu)響應(yīng)者(First Responder)
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
//判斷當(dāng)前點(diǎn)擊是否在控件的Bounds之內(nèi)
UIKit使用基于視圖的命中測(cè)試來(lái)確定觸摸事件發(fā)生的位置康谆。 具體來(lái)說柄慰,UIKit將觸摸位置與視圖層次結(jié)構(gòu)中的視圖對(duì)象的邊界進(jìn)行比較。 UIView的hitTest(_:with :)方法行進(jìn)視圖層次結(jié)構(gòu)哩都,尋找包含指定觸摸的最深的子視圖魁兼。 該視圖成為觸摸事件的第一響應(yīng)者
注意如果觸摸位置在視圖的邊界之外,則hitTest(_:with :)方法會(huì)忽略該視圖及其所有子視圖茅逮。 因此璃赡,當(dāng)視圖的clipsToBounds屬性為false時(shí),即使它們包含觸摸,也不會(huì)返回該視圖邊界之外的子視圖
事件的傳遞其實(shí)就是在事件產(chǎn)生于分發(fā)之后如何尋找最優(yōu)響應(yīng)的一個(gè)過程
你可以修改響應(yīng)者對(duì)象的next屬性來(lái)更改響應(yīng)者鏈,當(dāng)你修改了此屬性時(shí)下一個(gè)響應(yīng)者就是你返回的對(duì)象,
許多UIKit類已經(jīng)覆蓋此屬性并返回特定對(duì)象献雅。
UIView
對(duì)象,如果這個(gè)控制器的View是根視圖,那么next responder 就是viewController,否則,就是這個(gè)view的父視圖-
UIViewController
對(duì)象,- 如果該controller的view是window的根視圖,那么next responder就是window
- 如果controller是由另一個(gè)controller presented的,那么next responder是the presenting Controller.
UIWindow
對(duì)象,next responder 就是UIApplication
對(duì)象UIApplication
對(duì)象,那么 next responder 就是 app delegate ,但前提是appDelegate
是UIResponder
的一個(gè)實(shí)例,而不是view,viewcontroller或者是app本身.
(以上來(lái)自官方文檔)
事件的傳遞過程
1.觸碰屏幕產(chǎn)生事件UIEvent
并存入UIApplication
中事件隊(duì)列中,并在整個(gè)視圖結(jié)構(gòu)中自下而上進(jìn)行分發(fā),如下圖
2.UIWindow
接收到事件開始進(jìn)行最優(yōu)響應(yīng)視圖查詢過程(逆序遍歷子視圖)
3.當(dāng)?shù)?code>UiviewController這一層時(shí)同樣對(duì)其視圖開始最優(yōu)響應(yīng)視圖查詢,該查詢會(huì)調(diào)用上述提到的兩個(gè)方法,采用逆序查詢也是為了優(yōu)化查找速度,畢竟后添加的視圖易于命中
命中查找流程
1.調(diào)用hitTest
方法進(jìn)行最優(yōu)響應(yīng)視圖查找
- hidden = YES;
- userInteractionEnabled = NO;
- alpha < 0.01
這三種情況hitTest方法會(huì)返回nil ,即當(dāng)前視圖下無(wú)最有相應(yīng)視圖,無(wú)法響應(yīng)事件
2.hitTest方法內(nèi)部調(diào)用pointInside方法對(duì)點(diǎn)擊進(jìn)行是否在當(dāng)前視圖bounds內(nèi)進(jìn)行判斷,如果超出bounds范圍,則hitTest返回nil,未超出范圍繼續(xù)步驟3
3.對(duì)當(dāng)前視圖下的subviews進(jìn)行逆序步驟1,2查詢最優(yōu)響應(yīng)者,如果hitTest返回視圖,則說明當(dāng)前視圖有最優(yōu)響應(yīng)者,可能是self也可能是subview,
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.alpha < 0.01 || !self.userInteractionEnabled || self.hidden) {
return nil;
}
if (![self pointInside:point withEvent:event]) {
return nil;
}
__block UIView *hitView = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
hitView = [subview hitTest:point withEvent:event];
if (hitView) {
*stop = YES;
}
}];
return hitView ? : self;
}
總結(jié)
- 事件分發(fā)與傳遞:自上而下(
UIApplication
-window
-.....) - 事件響應(yīng):自下而上(
view
-superView
-.........)
打印結(jié)果: