iOS響應者鏈的具體應用-UI層級的精簡和控件的封裝

當用戶手指點擊屏幕後,響應事件會按照響應者鏈逐級的找到應該響應該事件的控件。咱們也能夠本身經過代碼來控制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響應者鏈的具體應用-異形按鈕

相關文章
相關標籤/搜索