問題還原:當(dāng)我們需要收起
TextField
的鍵盤時,通常的做法一般是在touchBegan
方法中放棄第一響應(yīng)者或者直接endEditing
。而當(dāng)我們把一個TableView
添加到控制器的View
上時,touchBegan
方法會不響應(yīng),原因就在于事件被TableView
攔截了
iOS的事件響應(yīng)鏈
事件響應(yīng)鏈,顧名思義就是由一系列事件響應(yīng)者構(gòu)成的一個響應(yīng)層次。當(dāng)我們點(diǎn)擊了手機(jī)屏幕上一點(diǎn)時,系統(tǒng)會通過一系列的方法找到應(yīng)該由哪一個視圖來響應(yīng)我們的點(diǎn)擊事件。系統(tǒng)是通過hitTest由UIWindow一層層向下遍歷找到可以響應(yīng)點(diǎn)擊事件的子視圖,知道某一個視圖沒有可以響應(yīng)事件的子視圖時,那么這個視圖就是我們所說的第一響應(yīng)者。我們可以寫個例子來看這個過程。
事件響應(yīng)鏈的形成過程
假設(shè)我們有下圖的層次結(jié)構(gòu)
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的View對象,第二個方法是返回點(diǎn)擊的點(diǎn)是否在某一個View坐標(biāo)范圍內(nèi)。我們可以通過給UIView寫一個分類來打印hitTest過程:
- (BOOL)kr_pointInside:(CGPoint)point withEvent:(UIEvent *)event{
BOOL canAnswer = [self kr_pointInside:point withEvent:event];
NSLog(@"%@ can answer: %d",self.class,canAnswer);
return canAnswer;
}
- (UIView *)kr_hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *answerView = [self kr_hitTest:point withEvent:event];
NSLog(@"hit view :%@",self.class);
return answerView;
}
當(dāng)我們點(diǎn)擊ViewC時,我們可以看到打印信息:
UIWindow can answer: 1
UIView can answer: 1
hit view :_UILayoutGuide
hit view :_UILayoutGuide
AView can answer: 1
DView can answer: 0
hit view :DView
hit view :UILabel
BView can answer: 1
hit view :UILabel
CView can answer: 1
hit view :UILabel
hit view :CView
hit view :BView
hit view :AView
hit view :UIView
hit view :UIWindow
UIStatusBarWindow can answer: 1
UIStatusBar can answer: 0
UIStatusBarForegroundView can answer: 0
UIStatusBarServiceItemView can answer: 0
UIStatusBarDataNetworkItemView can answer: 0
UIStatusBarBatteryItemView can answer: 0
UIStatusBarTimeItemView can answer: 0
從打印信息我們大概可以得到響應(yīng)者查找順序為:UIWindow->UIView->AView->DView->BView->CView
所以當(dāng)點(diǎn)擊ViewC時我們可以得到一個響應(yīng)者棧:
所以第一響應(yīng)者就是ViewC,如果ViewC不能響應(yīng),那么逐級向上查找,如果UIWindow也不響應(yīng),事件拋棄。
TableView為什么不響應(yīng)touchBegan
回到剛開始的問題,當(dāng)我們點(diǎn)擊TableView時,為什么touchBegan不響應(yīng)呢?通過響應(yīng)鏈我們不難想象到,當(dāng)我們點(diǎn)擊屏幕時,第一響應(yīng)者應(yīng)該是UITableView,而我們調(diào)用的touchBegan其實是ViewController的View的方法,所以無法被調(diào)用。
解決方法也很簡單,我們可以給tableView寫一個基類,重寫tableview的touchBegan方法,通過block或者代理傳出,然后繼承基類,即可實現(xiàn)touchBegan的響應(yīng)。