從iOS的事件響應(yīng)鏈看TableView為什么不響應(yīng)touchesBegan

問題還原:當(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)


層級關(guān)系.png

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)者棧:

C.png

所以第一響應(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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容