當用戶手指點擊屏幕後,響應事件會按照響應者鏈逐級的找到應該響應該事件的控件。咱們也能夠本身經過代碼來控制UI控件對於響應者鏈的判斷邏輯,來改變一個UI控件原本默認的響應邏輯。這裏不去解析響應鏈的遍歷順序,只舉例一個實際應用的場景。bash
首先解析關於響應鏈的一個方法:微信
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// ...
}
複製代碼
每當點擊事件發生後,相關的UI控件會按照響應鏈遍歷順序依次遞歸調用這個方法,返回的布爾值表明該點擊事件是否在當前UI控件內部,若是返回YES,會繼續向該UI控件內部的子控件去依次調用,直到找到最後的應該響應該點擊事件的UI控件。ide
因此,重寫這個方法能夠修改響應者鏈斷定的結果,若是固定返回NO,那麼該UI控件就不會攔截響應事件,響應事件會繼續向後傳遞。post
在一個界面中ui
正常的實現方式通常以下:spa
經過按鈕、蒙版、菜單欄的各類交互實現三個控件的移動、顯示邏輯才完成需求。code
只建立一個子控件 AlertView,AlertView建立時和界面的試圖大小一致,以達到覆蓋整個界面實現蒙版的功能。orm
將按鈕、菜單欄所有添加到AlertView中,使其稱爲AlertView的子控件。cdn
菜單欄由一個UITableView對象實現,默認的frame中的y爲屏幕的高度,這樣達到隱藏的效果:對象
- (UITableView *)tableView {
if (!_tableView) {
CGFloat x = 0;
CGFloat w = kScreenWidth;
CGFloat h = kCellHeight * 3 + (kIsIPhoneX ? 35 : 0);
CGFloat y = kScreenHeight;
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(x, y, w, h)];
_tableView.backgroundColor = [UIColor whiteColor];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.scrollEnabled = NO;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"alertcell"];
}
return _tableView;
}
複製代碼
浮動按鈕默認直接顯示在屏幕右側:
- (UIButton *)alertButton {
if (!_alertButton) {
_alertButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_alertButton setBackgroundImage:[UIImage imageNamed:@"tool"] forState:UIControlStateNormal];
_alertButton.frame = CGRectMake(kScreenWidth - 22, kScreenHeight - self.tableView.height - 10, 22, 43);
[_alertButton addTarget:self action:@selector(alertButtonClick:) forControlEvents:UIControlEventTouchUpInside];
}
return _alertButton;
}
複製代碼
點擊按鈕的時候,將菜單欄向上移動,按鈕右移隱藏,並同時改變AlertView的背景色,達到顯示蒙版的效果:
- (void)alertButtonClick:(UIButton *)sender {
[UIView animateWithDuration:0.3 animations:^{
sender.x = kScreenWidth;
self.tableView.y = kScreenHeight - self.tableView.height;
self.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.3];
} completion:^(BOOL finished) {
}];
}
複製代碼
重寫AlertView的touchesBegan:WithEvnet: 方法,點擊AlertView時,下移隱藏菜單欄,左移顯示按鈕,並將AlertView的背景色設置爲透明度,達到隱藏蒙版的效果:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self dismissAlert];
}
- (void)dismissAlert {
[UIView animateWithDuration:0.3 animations:^{
self.alertButton.x = kScreenWidth - self.alertButton.width;
self.tableView.y = kScreenHeight;
self.backgroundColor = [UIColor clearColor];
} completion:^(BOOL finished) {
}];
}
複製代碼
上面的代碼有一個很是大的問題,就是即使菜單欄隱藏的時候,AlertView會攔截屏幕的點擊事件。下面就是最重要的地方,重寫AlertView的pointInside: withEvent: 方法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// 當菜單欄隱藏的時候,只有點擊在按鈕區域內,纔會成爲響應者。
if (self.tableView.y == kScreenHeight) {
return CGRectContainsPoint(self.alertButton.frame, point);
} else { // 當菜單欄顯示時,採用默認的響應邏輯。
return [super pointInside:point withEvent:event];
}
}
複製代碼
當菜單欄顯示時,AlertView的使用默認的響應邏輯,攔截界面的點擊事件。
當菜單欄隱藏時,只有點擊區域是在AlertView的浮動按鈕範圍內時,纔會攔截點擊事件,不然AlertView會返回NO,響應者鏈遍歷過程當中會判斷AlertView屏幕點擊事件不在AlertView內,繼續遍歷到當前界面,並讓當前界面成爲響應者。
這樣就只須要爲當前界面添加一個子控件便可完成浮動按鈕、蒙版、菜單欄的所有需求。
利用上面的方式,還能夠應用在其它相似的應用場景。
能夠建立一個僅有菜單顯示面積大小的控件,重寫AlertView的pointInside: withEvent: 方法,直接返回YES,這樣這個菜單即便frame只有顯示面積那麼大,仍是可以攔截全屏幕的點擊事件。而後在重寫它的touchBegin方法去所以菜單欄便可。這樣就能夠省去建立一個屏幕大小的背景色爲透明的蒙版控件,就能夠達到攔截點擊菜單可見範圍之外區域隱藏菜單欄的需求。
好比一些給當前界面加修飾邊框的蒙版,邊框要攔截點擊事件,可是中間透明部分又能夠點擊界面。這裏就能夠經過重寫pointInside: withEvent: 判斷點擊區域是邊框範圍就返回YES,不然就返回NO。
好比相似於拼圖同樣的非正方體控件密集排列,還須要讓它們的點擊事件不能相互衝突,下面是一個具體實現的例子:iOS響應者鏈的具體應用-異形按鈕。