深入iOS事件處理層次及原理分析拴事、響應(yīng)鏈

1. iOS事件有哪一些

運(yùn)動(dòng)事件

  • 傳感器、計(jì)數(shù)器、陀螺儀

遠(yuǎn)程控制事件

  • 線控耳機(jī)

觸摸事件

  • 本文核心分析

2. 事件傳遞和響應(yīng)

2.1 原理分析

  • 當(dāng)我們手指觸碰到屏幕的時(shí)候猖腕,事件傳遞和響應(yīng)的流程是怎么樣的呢
  • 事件的流程圖
流程.png
  • IOKit.framework 為系統(tǒng)內(nèi)核的庫(kù)
  • SpringBoard.app 相當(dāng)于手機(jī)的桌面
  • Source1 主要接收系統(tǒng)的消息
  • Source0 - UIApplication - UIWindow
  • 從UIWindow 開始步驟,見下圖
window流程.png
  • 比如我們?cè)趕elf.view 上依次添加view1恨闪、view2倘感、view3(3個(gè)view是同級(jí)關(guān)系),那么系統(tǒng)用hitTest以及pointInside時(shí)會(huì)先從view3開始便利咙咽,如果pointInside返回YES就繼續(xù)遍歷view3的subviews(如果view3沒有子視圖老玛,那么會(huì)返回view3),如果pointInside返回NO就開始便利view2钧敞。
  • 反序遍歷蜡豹,最后一個(gè)添加的subview開始。也算是一種算法優(yōu)化

2.2 HitTest 溉苛、pointInside

  • 上一段層級(jí)關(guān)系的簡(jiǎn)單示例代碼
EOCLightGrayView *grayView = [[EOCLightGrayView alloc] initWithFrame:CGRectMake(50.f, 100.f, 260.f, 200.f)];
    
    redView = [[EOCRedView alloc] initWithFrame:CGRectMake(0.f, 0.f, 120.f, 100.f)];
    
    EOCBlueView *blueView = [[EOCBlueView alloc] initWithFrame:CGRectMake(140.f, 100.f, 100.f, 100.f)];
    
    EOCYellowView *yellowView = [[EOCYellowView alloc] initWithFrame:CGRectMake(50.f, 360.f, 200.f, 200.f)];
    
    [self.view addSubview:grayView];
    [grayView addSubview:redView];
    [grayView addSubview:blueView];
    [self.view addSubview:yellowView];
布局.png
打印.png
  • 點(diǎn)擊red镜廉,由于yellowgrey 同級(jí),yellowgrey 后添加愚战,所以先打印yellow娇唯,由于觸摸點(diǎn)不在yellow內(nèi),打印grey寂玲,然后遍歷grey塔插,打印他的兩個(gè)subviews
  • 通過在HitTest返回nilpointInside并沒有執(zhí)行拓哟,我們可以得知想许,pointInside調(diào)用順序你在HitTest之后的。
  • pointInside 的 參數(shù) :(CGPoint)poinit 的值是以自身為坐標(biāo)系的,判斷點(diǎn)是否view內(nèi)的范圍是以view自身的bounds為范圍流纹,而非frame
  • 如果在greyhitTest返回[super hitTest:point event:event]谎砾,則會(huì)執(zhí)行gery.subviews的遍歷(subviews 的 hitTest 與 pointInside)greypointInside 是判斷觸摸點(diǎn)是否在greybounds內(nèi)(不準(zhǔn)確)捧颅,greyhitTest 是判斷是否需要遍歷他的subviews.
  • pointInside 只是在執(zhí)行hitTest時(shí)景图,會(huì)在hitTest內(nèi)部調(diào)用的一個(gè)方法
  • pointInside 只是輔助hitTest的關(guān)系
  • hitTest是一個(gè)遞歸函數(shù)

2.3 hitTest 內(nèi)部實(shí)現(xiàn)代碼還原

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    ///hitTest:判斷pointInside,是不是在view里碉哑?是的話挚币,遍歷,不是的話返回nil;假設(shè)我就是點(diǎn)擊灰色的扣典,返回的是自己妆毕;
    NSLog(@"%s",__func__);
    
    NSArray *subViews = [[self.subviews reverseObjectEnumerator] allObjects];
    UIView *tmpView = nil;
    for (UIView *view in subViews) {
        
        CGPoint convertedPoint = [self convertPoint:point toView:view];
        if ([view pointInside:convertedPoint withEvent:event]) {
            
            tmpView = view;
            break;
            
        }
        
    }
    
    if (tmpView) {
        return tmpView;
    } else if([self pointInside:point withEvent:event]) {
        
        return self;
    } else {
        
        return nil;
        
    }
    
    
    return [self hitTest:point event:event];  //redView
    
    ///這里是hitTest的邏輯
    
    ///alpha(<=0.01)、userInterActionEnabled(NO)贮尖、hidden(YES)  pointInside返回的為NO
    
}

2.4 實(shí)戰(zhàn)之?dāng)U大button點(diǎn)擊區(qū)域

bigbutton.png
  • 紅色為button
  • 藍(lán)色為放大后的目標(biāo)點(diǎn)擊區(qū)域
  • 稍微注意是在bounds的基礎(chǔ)上修改
  • button 內(nèi)部的 hitTest 通過 pointInside 的確認(rèn)笛粘,來決定是否返回自己
@implementation EOCCustomButton

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    NSLog(@"%s", __func__);
   
    //擴(kuò)大它的響應(yīng)范圍
    CGRect frame = [self getScaleFrame];
    return CGRectContainsPoint(frame, point);
    
   //  return [super pointInside:point withEvent:event];
}

- (CGRect)getScaleFrame {
    
    CGRect rect = self.bounds;
    
    if (rect.size.width < 40.f) {
        rect.origin.x -= (40-rect.size.width)/2;
    }
    
    if (rect.size.height < 40.f) {
        rect.origin.y -= (40-rect.size.height)/2;
    }
    
    rect.size.width = 40.f;
    rect.size.height = 40.f;
    
    return rect;
}

2.5 UIRespond 與 響應(yīng)鏈的組成

  • 當(dāng)我們通過hitTest找到視圖后,我們產(chǎn)生的touch事件湿硝,他是怎么一層層響應(yīng)的薪前?
respondch.png
  • 響應(yīng)鏈?zhǔn)峭ㄟ^ nextResponder 屬性組成的一個(gè)鏈表
  • 點(diǎn)擊的 view 有 superView ,nextResponder 就是 superView ;
  • view.nextResponder .nextResponder 是viewController 或者是 view.superView. view
  • view.nextResponder .nextResponder. nextResponder 是 UIWindow (非嚴(yán)謹(jǐn),便于理解)
  • view.nextResponder .nextResponder. nextResponder. nextResponder 是UIApplication 、UIAppdelate关斜、直到nil (非嚴(yán)謹(jǐn),便于理解)
  • touch事件就是根據(jù)響應(yīng)鏈的關(guān)系來層層調(diào)用(我們重寫touch 要記得 super 調(diào)用示括,不然響應(yīng)鏈會(huì)中斷
  • 比如我們監(jiān)聽self.viewtouch事件,也是因?yàn)?code>subviews的touch都在同一個(gè)響應(yīng)鏈里

3. 手勢(shì)事件

3.1 手勢(shì) 與 hitTest 的關(guān)系

  • 相同上面的學(xué)習(xí)我們可以推測(cè)出痢畜,手勢(shì)的響應(yīng)也得必須經(jīng)過hitTest先找到視圖才能觸發(fā)(已驗(yàn)證)

3.2 手勢(shì)與 觸摸事件的關(guān)系

  • touch事件是UIView內(nèi)部的東西垛膝,而手勢(shì)疊加上去的觸摸事件
  • subview會(huì)響應(yīng)superview的手勢(shì), 但是同級(jí)的subview不會(huì)響應(yīng)

3.3 系統(tǒng)如何分辨手勢(shì)種類

  • 首先我們想在手勢(shì)中調(diào)用 touches 方法必須要導(dǎo)入
    #import <UIKit/UIGestureRecognizerSubclass.h>
    因?yàn)?code>gesture繼承的是NSObject 而不是 UIRespond
  • 通過嘗試不調(diào)用 tap手勢(shì)touchesBegan 丁稀,發(fā)現(xiàn)tap手勢(shì)無法響應(yīng)
  • 通過嘗試調(diào)用touchesBegan 吼拥,但是不調(diào)用 pan手勢(shì)touchesMoved ,發(fā)現(xiàn)pan手勢(shì)無法響應(yīng)
  • 我們通過UITouch的實(shí)例线衫,可以看到里面有很多屬性凿可,比如點(diǎn)擊的次數(shù),上次的位置等桶雀,結(jié)合這個(gè)屬性系統(tǒng)與touches方法就可以判斷出你使用的是什么手勢(shì)

3.4 手勢(shì)與view的touches事件的關(guān)系

  • 首先通過觸摸事件矿酵,先響應(yīng)touchesBegan 以及 touchesMoved,直到手勢(shì)被識(shí)別出來矗积,調(diào)用touchesCancelled全肮,全權(quán)交給手勢(shì)處理。
  • 但是我們可以改變這種關(guān)系
下面是系統(tǒng)的默認(rèn)設(shè)置
 tapGesture.delaysTouchesBegan = NO;   
///是否延遲view的touch事件識(shí)別棘捣;如果延遲了(YES)辜腺,并且手勢(shì)也識(shí)別到了,touch事件會(huì)被拋棄

 tapGesture.cancelsTouchesInView = YES;   
///識(shí)別手勢(shì)之后,是否取消view的touch事件
// 如果為NO, touchesCancelled 不會(huì)調(diào)用评疗,取而代之的是手勢(shì)結(jié)束后touchesEnd

4. button事件

4.1 系統(tǒng)是如何分辨UIControlEvent

  • 我們還是通過button內(nèi)部的touches來實(shí)踐
  • 實(shí)踐過程略测砂,與手勢(shì)同理
  • 比如說 touchUpInside,通過查看堆棧調(diào)用百匆,我們發(fā)現(xiàn)在touchesEnd后完成對(duì)touchUpInside的識(shí)別砌些,然后再調(diào)起sendAction:方法

5. 觸摸事件的運(yùn)用

  • 上文已經(jīng)講過一個(gè)button點(diǎn)擊范圍擴(kuò)大的案例,再講一個(gè)案例
case2.png
  • 與上個(gè)例子不同的是加匈,當(dāng)我們點(diǎn)擊黑色的時(shí)候存璃,因?yàn)樵?code>greyview的外面,別說響應(yīng)黑色button了雕拼,我們直接不會(huì)響應(yīng)greyview了纵东,怎么辦?
  • 一種是在self.view-pointInside 返回 YES, 不過這種在交互復(fù)雜的場(chǎng)景不存在實(shí)用性
  • 我們可以重寫 self.view-hitTest, 把當(dāng)前觸摸的點(diǎn)分別轉(zhuǎn)化為subviews上的坐標(biāo)系的點(diǎn)啥寇,在用subviewspointinside判斷此點(diǎn)偎球,然后返回對(duì)應(yīng)的subviews
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辑甜,隨后出現(xiàn)的幾起案子衰絮,更是在濱河造成了極大的恐慌,老刑警劉巖栈戳,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岂傲,死亡現(xiàn)場(chǎng)離奇詭異难裆,居然都是意外死亡子檀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門乃戈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褂痰,“玉大人,你說我怎么就攤上這事症虑∷跬幔” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵谍憔,是天一觀的道長(zhǎng)匪蝙。 經(jīng)常有香客問我,道長(zhǎng)习贫,這世上最難降的妖魔是什么逛球? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮苫昌,結(jié)果婚禮上颤绕,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好奥务,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布物独。 她就那樣靜靜地躺著,像睡著了一般氯葬。 火紅的嫁衣襯著肌膚如雪挡篓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天帚称,我揣著相機(jī)與錄音瞻凤,去河邊找鬼。 笑死世杀,一個(gè)胖子當(dāng)著我的面吹牛阀参,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞻坝,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蛛壳,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了所刀?” 一聲冷哼從身側(cè)響起衙荐,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浮创,沒想到半個(gè)月后忧吟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斩披,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年溜族,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垦沉。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡煌抒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厕倍,到底是詐尸還是另有隱情寡壮,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布讹弯,位于F島的核電站况既,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏组民。R本人自食惡果不足惜棒仍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邪乍。 院中可真熱鬧降狠,春花似錦对竣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蛋褥,卻和暖如春临燃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烙心。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工膜廊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人淫茵。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓爪瓜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親匙瘪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铆铆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • 在iOS開發(fā)中經(jīng)常會(huì)涉及到觸摸事件。本想自己總結(jié)一下丹喻,但是遇到了這篇文章薄货,感覺總結(jié)的已經(jīng)很到位,特此轉(zhuǎn)載碍论。作者:L...
    WQ_UESTC閱讀 5,989評(píng)論 4 26
  • 好奇觸摸事件是如何從屏幕轉(zhuǎn)移到APP內(nèi)的谅猾?困惑于Cell怎么突然不能點(diǎn)擊了?糾結(jié)于如何實(shí)現(xiàn)這個(gè)奇葩響應(yīng)需求鳍悠?亦或是...
    Lotheve閱讀 56,661評(píng)論 51 597
  • 值得注意的事,當(dāng)一個(gè)view上面有多個(gè)手勢(shì)時(shí),touch是無序的 1事件是怎么樣產(chǎn)生與傳遞的?(由上至下的過程) ...
    池鵬程閱讀 2,221評(píng)論 0 8
  • 在開發(fā)過程中税娜,大家或多或少的都會(huì)碰到令人頭疼的手勢(shì)沖突問題,正好前兩天碰到一個(gè)類似的bug贼涩,于是借著這個(gè)機(jī)會(huì)了解了...
    閆仕偉閱讀 5,294評(píng)論 2 23
  • iOS中有三類事件:UIEventTypeTouches觸摸事件巧涧、 UIEventTypeMotion “動(dòng)作”事...
    WeiHing閱讀 36,963評(píng)論 7 69