iOS事件蓝厌,原來如此

精簡地說:iOS事件分為傳遞和響應兩個部分诞帐。

事件傳遞(建立傳遞鏈):

iOS系統(tǒng)檢測到手指觸摸(Touch)操作時會將其打包成一個UIEvent對象,并放入當前活動Application的事件隊列祸穷,單例的UIApplication會從事件隊列中取出觸摸事件并傳遞給單例的UIWindow來處理性穿,UIWindow對象首先會使用hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖雷滚,這個過程稱之為hit-test view需曾。
hittest的目的就是找到最終的傳遞鏈

hitTest:withEvent:流程如下:

  1. 先判斷當前視圖hidden=YES祈远,userInteractionEnabled=NO呆万,alpha<0.01等屬性,如果滿足其中之一车份,返回nil谋减。
  2. 再看當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內(nèi);
  3. 若返回NO,則hitTest:withEvent:返回nil;
  4. 若返回YES,則向當前視圖的【一級子視圖(subviews)遞歸發(fā)送】hitTest:withEvent:消息扫沼,所有子視圖的遍歷順序是【從subviews數(shù)組的末尾向前遍歷】逃顶,直到有子視圖返回非空對象或者全部子視圖遍歷完畢;
  5. 若第一次有子視圖返回非空對象充甚,則hitTest:withEvent:方法返回此對象,處理結束霸褒;如所有子視圖都返回非伴找,則hitTest:withEvent:方法返回自身。

提醒:
hittest返回nil表示該條傳遞鏈已經(jīng)終止废菱,不是正確的傳遞鏈技矮。
hittest返回非nil對象表示已經(jīng)找到傳遞鏈的葉子節(jié)點,即找到正確的傳遞鏈殊轴。

hitTest:withEvent:遇到以下會返回nil衰倦。
1.hidden=YES的視圖。
2.userInteractionEnabled=NO的視圖(注意userInteractionEnabled是影響子視圖事件傳遞旁理,但不影響兄弟視圖)
3.alpha<0.01的視圖樊零。
4.顯示區(qū)域超過父視圖bounds區(qū)域的視圖。那么超出區(qū)域不能識別。當然驻襟,可以重寫pointInside:withEvent:方法來識別夺艰。

hitTest:底層實現(xiàn)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.判斷自己能否接收觸摸事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2.判斷觸摸點在不在自己范圍內(nèi)
    if (![self pointInside:point withEvent:event]) return nil;
    // 3.從后往前遍歷自己的子控件,看是否有子控件更適合響應此事件
    int count = self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        CGPoint childPoint = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childPoint withEvent:event];
        if (fitView) {
            return fitView;
        }
    }
    // 沒有找到比自己更合適的view
    return self;
}

結合例子分析:


例子.png

舉例分析:用戶點擊了View D沉衣,下面結合上圖介紹hit-test view的流程:
1郁副、A是UIWindow的根視圖,因此豌习,UIWindwo對象會首先對A進行hit-test存谎;
2、顯然用戶點擊的范圍是在A的范圍內(nèi)肥隆,因此羊异,pointInside:withEvent:返回了YES,這時會繼續(xù)檢查A的子視圖伦忠;
3滤蝠、這時候會有兩個分支,B和C:
C在subviews的末尾嘱巾,先遞歸遍歷C憨琳。點擊的范圍在C內(nèi),即C的pointInside:withEvent:返回YES旬昭;
4篙螟、這時候有D和E兩個分支:
點擊的范圍不再E內(nèi),因此E的pointInside:withEvent:返回NO问拘,對應的hitTest:withEvent:返回nil遍略;
點擊的范圍在D內(nèi),即D的pointInside:withEvent:返回YES骤坐,由于D沒有子視圖绪杏,因此,D的hitTest:withEvent:會將D返回纽绍,再往回回溯蕾久,就是C的hitTest:withEvent:返回D--->>A的hitTest:withEvent:返回D。
即A->C->E(返回nil)->D(返回D)->C(返回D)->A(返回D)
至此拌夏,本次點擊事件的第一響應者就通過響應者鏈的事件分發(fā)邏輯成功的找到了僧著。
不難看出,這個處理流程有點類似二分搜索的思想障簿,這樣能以最快的速度盹愚,最精確地定位出能響應觸摸事件的UIView。
另外hittest可能會被調用2-3次站故,這里請不要再hittest作業(yè)務相關操作皆怕,否則會導致執(zhí)行多次。

事件響應(回溯響應鏈):

經(jīng)過hittest后,我們已經(jīng)找到了鏈尾【第一響應者】端逼,這時候開始回溯響應操作朗兵。響應鏈的關系如圖:
請注意:響應鏈在傳遞鏈的基礎上增加了UIViewController。

UIResponder.png

NextResponder:
1.當一個view被添加到superView上的時候顶滩,它的nextResponder就會被指向它的superView余掖;
2.當vc被初始化的時候,self.view(topmost view)的nextResponder會被指向所在的controller礁鲁;
(概括前兩者就是:如果當前這個view是控制器的self.view,那么控制器就是上一個響應者 如果當前這個view不是控制器的view,那么父控件就是上一個響應者)
3.vc的nextResponder會被指向self.view的superView盐欺。
4.最頂級的vc的nextResponder指向UIWindow。
5.UIWindow的nextResponder指向UIApplication

在事件響應對象UIResponder(UIView和UIViewController都是繼承UIResponder)中有對應的方法來分別處理這幾個階段的事件:
touchesBegan:NSArray<UITouch *>>withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
Touch默認流程(以單擊為例)是:從響應鏈葉子節(jié)點開始回溯仅醇,先是TouchBegan回溯冗美,然后是TouchEnded回溯。

舉例:對于觸摸事件來說析二,UIApplication會首先把事件交給keyWindow,Window會將事件交給UIGestureRecognizer處理粉洼,如果UIGestureRecognizer識別了傳遞過來的事件,則交給相對應的target去處理叶摄,回溯終止属韧,事件不會再傳遞!當然這里也可以重寫touchBegan等方法手動讓手勢繼續(xù)往superview傳從而實現(xiàn)多級響應蛤吓。
如果UIGestureRecognizer并沒有識別傳遞過來的事件(可能是沒有視圖添加手勢宵喂,也可能手勢識別不成功),事件會傳遞到視圖樹形結構

touches方法實際上什么事都沒做会傲,UIView繼承了它進行重寫锅棕,就是把事件傳遞給nextResponder,相當于[self.nextResponder touchesBegan:touches withEvent:event]淌山。所以當一個view沒有重寫touch事件裸燎,那么這個事件就會一直傳遞下去,直到UIApplication泼疑。如果重寫了touch方法德绿,這個view響應了事件之后,事件就被攔截了王浴,它的nextResponder不會收到這個事件。這個時候如果想事件繼續(xù)傳遞下去梅猿,可以調用[self.nextResponder touchesBegan:touches withEvent:event]

手勢:

通過touches方法監(jiān)聽view觸摸事件氓辣,有很明顯的幾個缺點:必須得自定義view、由于是在view內(nèi)部的touches方法中監(jiān)聽觸摸事件袱蚓,因此默認情況下钞啸,無法讓其他外界對象監(jiān)聽view的觸摸事件、不容易區(qū)分用戶的具體手勢行為。
所以iOS把觸摸事件做了封裝, 對常用的手勢進行了處理, 封裝了6種常見的手勢
UITapGestureRecognizer(敲擊)
UILongPressGestureRecognizer(長按)
UISwipeGestureRecognizer(輕掃)
UIRotationGestureRecognizer(旋轉)
UIPinchGestureRecognizer(捏合体斩,用于縮放)
UIPanGestureRecognizer(拖拽)

UIControlEvent:

其實要了解UIControlEvent梭稚,必須簡單說一下UITouch和UIEvent事件,都是和觸摸相關絮吵,UIEvent是一系列UITouch的集合弧烤,在IOS中負責響應觸摸事件。

UIControl是UIView的子類蹬敲,當然也是UIResponder的子類暇昂。UIControl是諸如UIButton、UISwitch伴嗡、UITextField等控件的父類急波,它本身也包含了一些屬性和方法。

UIControl對象采用了一種新的事件處理機制瘪校,將觸摸事件轉換成簡單操作澄暮,其實就是重寫了UIResponder的方法中(如touchBegan:withEvent)中,即事件不再往上回溯響應阱扬。這樣方便了事件處理泣懊,而不用每次都重寫TouchBegan方法。
UIResponder可以參考這篇文章UIKit: UIResponder价认。
比如我點擊一個UIButton嗅定,即使你未添加UIControlEventTouchUpInside,它的父類touchBegan也不會被調用用踩。因為UIControl重寫的方法touchBegan:withEvent并未調用[super touchBegan:withEvent]

UITapGestureRecognzier:

UITapGestureRecognzier其實就是對各類復雜觸摸操作響應過程的一個封裝渠退。
在六種手勢識別中,只有一種手勢是離散手勢脐彩,它就是UITapGestureRecognzier碎乃。離散手勢的特點就是一旦識別就無法取消,而且只會調用一次手勢操作事件(初始化手勢時指定的觸發(fā)方法)惠奸。換句話說其他五種手勢是連續(xù)手勢梅誓,連續(xù)手勢的特點就是會多次調用手勢操作事件,而且在連續(xù)手勢識別后可以取消手勢佛南」j【下圖是手勢狀態(tài)圖】

手勢狀態(tài).png

分別以UITap和UIPan兩種手勢說明流程:
UITap:TouchBegan回溯->Tap->TouchCancelled回溯。
UIPan:TouchBegan回溯->TouchMoved多次回溯->UIPanBegan-TouchCancelled回溯->UIPanChanged多次->UIPanEnded嗅回。

事件總結:
1.父視圖不能接收事件及穗,則子視圖無法接受事件
2.子視圖超出父視圖的部分,不能接收事件
3.同一個父視圖下绵载,最上面的視圖埂陆,首先遭遇事件苛白,如果能夠響應,就不向下傳遞事件焚虱。如果不能接收购裙,事件向下傳遞

總結:

  1. iOS事件流程分為尋找響應鏈和響應鏈回溯,其中響應鏈回溯部分和android類似鹃栽。
  2. 為了事件響應處理的方便躏率,蘋果又推出了UIControlEvent和UITapGestureRecognzier。它們都是針對響應鏈回溯過程谍咆。
  3. UITapGestureRecognzier和UIControlEvent不同禾锤,UIControlEvent不會響應父類的TouchBegan等操作,而UITapGestureRecognzier會響應摹察。

參考:
iOS開發(fā)系列--觸摸事件恩掷、手勢識別、搖晃事件供嚎、耳機線控

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
禁止轉載黄娘,如需轉載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末克滴,一起剝皮案震驚了整個濱河市逼争,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劝赔,老刑警劉巖誓焦,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異着帽,居然都是意外死亡杂伟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門仍翰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赫粥,“玉大人,你說我怎么就攤上這事予借≡狡剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵灵迫,是天一觀的道長秦叛。 經(jīng)常有香客問我,道長瀑粥,這世上最難降的妖魔是什么挣跋? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮利凑,結果婚禮上浆劲,老公的妹妹穿的比我還像新娘。我一直安慰自己哀澈,他們只是感情好牌借,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著割按,像睡著了一般膨报。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上适荣,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天现柠,我揣著相機與錄音,去河邊找鬼弛矛。 笑死够吩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的丈氓。 我是一名探鬼主播周循,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼万俗!你這毒婦竟也來了湾笛?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤闰歪,失蹤者是張志新(化名)和其女友劉穎嚎研,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體库倘,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡临扮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了于樟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片公条。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖迂曲,靈堂內(nèi)的尸體忽然破棺而出靶橱,到底是詐尸還是另有隱情,我是刑警寧澤路捧,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布关霸,位于F島的核電站,受9級特大地震影響杰扫,放射性物質發(fā)生泄漏队寇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一章姓、第九天 我趴在偏房一處隱蔽的房頂上張望佳遣。 院中可真熱鬧识埋,春花似錦、人聲如沸零渐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诵盼。三九已至惠豺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間风宁,已是汗流浹背洁墙。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留戒财,地道東北人热监。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像饮寞,于是被迫代替她去往敵國和親狼纬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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

  • 好奇觸摸事件是如何從屏幕轉移到APP內(nèi)的骂际?困惑于Cell怎么突然不能點擊了疗琉?糾結于如何實現(xiàn)這個奇葩響應需求?亦或是...
    Lotheve閱讀 56,661評論 51 597
  • 在iOS開發(fā)中經(jīng)常會涉及到觸摸事件歉铝。本想自己總結一下盈简,但是遇到了這篇文章,感覺總結的已經(jīng)很到位太示,特此轉載柠贤。作者:L...
    WQ_UESTC閱讀 5,989評論 4 26
  • iOS開發(fā)中的事件處理 理論非原創(chuàng),是對網(wǎng)上資料的整理以及Demo驗證 一. UIResponder 1.1 事件...
    喪心病狂樂閱讀 661評論 0 0
  • 在原文 http://www.cnblogs.com/mcj-coding/p/3569908.html進行了補充...
    WHZ鬧哪樣閱讀 2,359評論 0 4
  • 本文來自:http://ios.jobbole.com/84081/ 前言: 按照時間順序,事件的生命周期是這樣的...
    HackerOnce閱讀 2,831評論 1 10