iOS-事件傳遞,響應(yīng)者鏈條及常見(jiàn)面試題

在iOS中只有繼承UIResponder的對(duì)象才能夠接收并處理事件,UIResponder 是所有響應(yīng)對(duì)象的基類,在UIResponder類中定義了處理上述各種事件的接口。我們熟悉的 UIApplication、 UIViewController、 UIWindow 和所有繼承自UIView的UIKit類都直接或間接的繼承自UIResponder,所以它們的實(shí)例都是可以構(gòu)成響應(yīng)者鏈的響應(yīng)者對(duì)象,首先我們通過(guò)一張圖來(lái)簡(jiǎn)單了解一下事件的傳遞以及響應(yīng).


hl.png

1.響應(yīng)者鏈條

響應(yīng)者鏈條就是由多個(gè)響應(yīng)者對(duì)象連接起來(lái)的鏈條,它的作用就是讓我們能夠清楚的看見(jiàn)每個(gè)響應(yīng)者之間的聯(lián)系,并且可以讓一個(gè)時(shí)間多個(gè)對(duì)象處理.

2.響應(yīng)過(guò)程

iOS系統(tǒng)檢測(cè)到手指觸摸(Touch)操作時(shí)會(huì)將其打包成一個(gè)UIEvent對(duì)象,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列,單例的UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來(lái)處理,UIWindow對(duì)象首先會(huì)使用hitTest:withEvent:方法尋找此次Touch操作初始點(diǎn)所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖(最合適來(lái)處理的控件),這個(gè)過(guò)程稱之為hit-test view。

那么什么是最適合來(lái)處理事件的控件?
1.自己能響應(yīng)觸摸事件
2.觸摸點(diǎn)在自己身上
3.從后往前遞歸遍歷子控件, 重復(fù)上兩步
4.如果沒(méi)有符合條件的子控件, 那么就自己最合適處理

3.兩個(gè)重要的響應(yīng)方法(UIView的)

1.hit-test view:事件傳遞給控件的時(shí)候, 就會(huì)調(diào)用該方法,去尋找最合適的view并返回看可以響應(yīng)的view

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.如果控件不允許與用用戶交互,那么返回nil
    if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES){
        return nil;
    }
    // 2. 如果點(diǎn)擊的點(diǎn)在不在當(dāng)前控件中,返回nil
    if (![self pointInside:point withEvent:event]){
        return nil;
    }
    // 3.從后往前遍歷每一個(gè)子控件
    for(int i = (int)self.subviews.count - 1 ; i >= 0 ;i--){
        // 3.1獲取一個(gè)子控件
        UIView *childView = self.subviews[i];
        // 3.2當(dāng)前觸摸點(diǎn)的坐標(biāo)轉(zhuǎn)換為相對(duì)于子控件觸摸點(diǎn)的坐標(biāo)
        CGPoint childP = [self convertPoint:point toView:childView];
        // 3.3判斷是否在在子控件中找到了更合適的子控件(遞歸循環(huán))
        UIView *fitView = [childView hitTest:childP withEvent:event];
        // 3.4如果找到了就返回
        if (fitView) {
            return fitView;
        }
    }
    // 4.沒(méi)找到,表示沒(méi)有比自己更合適的view,返回自己
    return self;
}
  1. pointInside: 該方法判斷觸摸點(diǎn)是否在控件身上,是則返回YES,否則返回NO,point參數(shù)必須是方法調(diào)用者的坐標(biāo)系.
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    return NO;
}

4.涉及的相關(guān)面試題

1.事件的傳遞方向: 事件傳遞是從上自下傳遞,響應(yīng)是從下到上,所謂的上就是父視圖而已,也就是離窗口最近的.

2.穿透控件:
2.1 如果我們不想讓某個(gè)視圖響應(yīng)事件,只需要重載 PointInside:withEvent:方法,讓此方法返回NO就行了.
2.2 若是view上有view1,view1上有view2,點(diǎn)擊view2,view2自己響應(yīng),點(diǎn)擊view1,view1不響應(yīng),只有view響應(yīng),也就是隔層傳遞

/*
 重載view1的此方法,如果點(diǎn)在自己身上,且子控件中有最合適的響應(yīng)者,就返回對(duì)應(yīng)子控件,否則就不響應(yīng),并將該事件隨著響應(yīng)者鏈條往回傳遞,交給上一個(gè)響應(yīng)者來(lái)處理. (即調(diào)用super的touches方法)
 
 誰(shuí)是上一個(gè)響應(yīng)者?
 1. 如果view的控制器存在,就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父視圖
 2. 在視圖層次結(jié)構(gòu)的最頂級(jí)視圖,如果也不能處理收到的事件或消息,則其將事件傳遞給window對(duì)象進(jìn)行處理
 3. 如果window對(duì)象也不處理,則其將事件或消息傳遞給UIApplication對(duì)象
 4. 如果UIApplication也不能處理該事件或消息,則將其丟棄
*/
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    
    CGRect frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
    BOOL value = (CGRectContainsPoint(frame, point));
    NSArray *views = [self subviews];
    for (UIView *subview in views) {
        value = (CGRectContainsPoint(subview.frame, point));
        if (value) {
            return value;
        }
    }
    return NO;
}

例如放大控件響應(yīng)區(qū)域,view上有n個(gè)子視圖,點(diǎn)擊其中一個(gè)讓另一個(gè)來(lái)響應(yīng)等等,都是可以通過(guò)重載pointInside來(lái)達(dá)到目的.

如有問(wèn)題,歡迎指正分享~
.End

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容