最好的輸入方式:iOS中的觸摸事件

喬幫主在發(fā)布會上提到盏求,用戶的手才是最好的輸入設備抖锥,的確亿眠,iPhone之后,非觸屏手機再已難覓磅废。觸摸是最基本的用戶輸入事件缕探,理解iOS特有的觸摸事件響應機制,能夠良好管理程序中觸摸響應方法还蹲,避免沖突的發(fā)生爹耗。

iOS中的事件

iOS中的事件主要分為三類:

  1. UIControl Actions: 使用target/action注冊的SEL。
  2. User Events: 用戶與應用之間的交互:觸摸谜喊,輸入文字潭兽,搖晃,遠程控制等斗遏。
  3. System Events: 應用啟動山卦,切前后臺,低內(nèi)存等诵次。
    cocoa和cocoa touch的程序啟動后账蓉,,會首先初始化一些基本資源:在主線程創(chuàng)建一個main event loop逾一;初始化主UIWindow铸本。
    應用啟動過程-w500

    main event loop本質(zhì)上是一個NSRunLoop,與其他輔助線程的run loop不同遵堵,其是自創(chuàng)建后自動開始運行的箱玷。主消息循環(huán)最大的特點是:它在創(chuàng)建時就與負責捕獲用戶事件的系統(tǒng)底層建立了連接,所以它的input source可以收到系統(tǒng)傳遞過來的用戶事件陌宿。UIApplication對象會將當前要處理的用戶事件封裝成UIEvent,發(fā)送給UIWindow,在由UIWindow轉(zhuǎn)發(fā)給對應的響應者锡足。
    iOS響應用戶事件
    iOS響應用戶事件

    UIEvent表示用戶與iOS產(chǎn)生交互的事件,UIWindow將觸摸事件發(fā)送給hitTest View,其他事件發(fā)送給first responder壳坪,若它們不能處理該事件舶得,事件在響應鏈向上傳遞,找到最終的響應者或丟棄爽蝴。
    本文主要介紹觸摸事件的響應機制沐批。

iOS中能夠捕獲觸摸事件的類

iOS程序中,有三種類可以接受用戶的觸摸事件并響應霜瘪,分別是:UIControl, UIReponder, UIGestureRecognizer珠插,這三個類在參與觸摸響應機制的時機不同,在實際使用時要加以注意颖对。

iOS中的觸摸事件

iOS中使用UItouch來表示用戶的一根手指在屏幕上的觸摸行為捻撑。當用戶觸摸屏幕時,硬件會捕捉到觸摸行為,將觸摸點的半徑顾患、力度和坐標等發(fā)送給iOS番捂,經(jīng)過UIKit封裝后,得到UITouch對象江解。通過UITouch對象设预,我們可以獲得其關(guān)聯(lián)的視圖(hitTest View),在視圖中的坐標,生命周期的當前階段犁河,點擊數(shù)等信息鳖枕。。一次用戶點擊多次的事件桨螺,其只包含一個UITouch
觸摸類型的UIEvent包含至少一個UITouch宾符,也就是用戶在屏幕上的一次手勢操作的手指運動,其會持有此次事件相關(guān)聯(lián)的UITouches序列灭翔。魏烫,即在一次手勢操作中,其中一個手指中途離開屏幕肝箱,它所對應的UITouch依然存在于該事件中哄褒。響應者會在touchesBegan:withEvent:等方法中獲取UITouch對應的UIEvent
UITouches序列在用戶第一根手指觸摸屏幕時開始煌张,最后一根手指離開時結(jié)束呐赡,當手指狀態(tài)變化時,iOS會將序列中的UITouch對象發(fā)送給UIEvent對象唱矛。

一個UItouches序列和UItouch的不同生命周期
一個UItouches序列和UItouch的不同生命周期

iOS的觸摸事件響應機制

當用戶觸摸屏幕時罚舱,對應的觸摸事件會加入到UIApplication事件隊列中井辜,當下一個RunLoop來臨時绎谦,UIApplication會將出列最前端的事件,發(fā)送給當前的UIWindow(key window)粥脚。
UIWindow會調(diào)用hitTest:withEvent:方法窃肠,開始hit-testing流程尋找包含觸摸點的視圖。該流程會返回包含觸摸點的層級最低的視圖刷允。
每當用戶觸摸屏幕時冤留,UIKit都會執(zhí)行hit-testing,之后再從hitTest視圖開始尋找事件的響應者树灶。當hitTest視圖決定后纤怒,它就關(guān)聯(lián)了對應的觸摸事件,會持續(xù)收到觸摸事件生命周期的方法,(touchBegan, touchMove, touchCancel/touchEnd),即使是觸摸點已經(jīng)在touchMove階段移出了hitTest視圖天通,它依然能夠收到后續(xù)的消息泊窘。

Note: A touch object is associated with its hit-test view for its lifetime, even if the touch later moves outside the view.

iOS的觸摸事件響應機制

hit-testing流程

iOS中hit-testing使用逆前序的深度遍歷算法來確定用戶點按的最低層級(最靠近用戶)的視圖,該hitTest視圖是觸摸事件的響應鏈頭結(jié)點。
逆前序的深度遍歷算法:根節(jié)點-->右子樹-->左子樹烘豹。
當收到觸摸事件后,UIApplication在當前視圖層級中,從key window開始(最頂級)揣钦,從上往下遍歷子視圖調(diào)用hitTest:withEvent:移层,若找到hitTest視圖則停止遍歷并返回。
當視圖收到hitTest:withEvent:方法后憔鬼,通過下列條件判斷是否在該視圖執(zhí)行hit-testing龟劲。

  1. pointInside:withEvent:方法返回YES。pointInside:withEvent:方法用來判斷觸摸點是否在當前視圖內(nèi)轴或。
  2. hidden == NO咸灿。
  3. userInteractionEnabled == YES。
  4. alpha >= 0.01侮叮。若view的content繪制為透明的避矢,則不受影響。

需要注意的是囊榜,當clipsToBounds == NO時审胸,視圖的子視圖可能會超出其bounds,這種情況如果觸摸點在子視圖超出父視圖的范圍卸勺,那么hit-tesing不會再此視圖樹上執(zhí)行砂沛。

hit-testing

如圖,當用戶觸摸viewB.1時曙求,UIApplication對象收到觸摸事件碍庵,從key window開始執(zhí)行hit-testing,首先訪問viewC悟狱,由于pointInside:withEvent:方法返回NO静浴,取消執(zhí)行并訪問viewB,滿足執(zhí)行挤渐,則從右往左開始訪問其子視圖(視圖層級從下往上)苹享,找到viewB.1,它沒有子視圖浴麻,則返回自己得问。最終UIWindow對象將viewB.1作為hitTest視圖返回給UIApplication對象。
hit-testing流程圖

可以看到软免,當某一視圖收到hitTest:withEvent:方法后宫纬,它會向所有子視圖發(fā)送hitTest:withEvent:方法,若它的沒有子視圖或所有子視圖返回nil膏萧,那么就返回自己漓骚,所有hit-testing流程最終一定會找到一個對象UIView/UIWindow去接收觸摸事件宣蔚。
以下是hitTest:withEvent:可能的實現(xiàn)。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

responder chain

responder chain是UIResponder對象組成的鏈形結(jié)構(gòu)认境,它以first responder為頭結(jié)點胚委,UIApplication對象為尾節(jié)點,事件從頭開始在響應鏈中向上傳遞叉信。
UIResponder用來設計處理事件亩冬,UIApplication, UIViewController, UIView都是其子類,只要它們實現(xiàn)了UIResponder中的鉤子方法硼身,就可以響應對應的事件硅急。

UIResponder的繼承關(guān)系
UIResponder的繼承關(guān)系

其中first responder用來第一個接觸事件,可以使用becomeFirstResponder來設置它佳遂,主要要在視圖層級已經(jīng)完全建立之后再設置营袜。

If you try to assign the first responder in viewWillAppear:, your object graph is not yet established, so the becomeFirstResponder method returns NO

默認情況下,fist responder是當前UIWindow中最有可能響應事件的UIView丑罪,這由UIkit決定荚板。
iOS中大部分的事件都依賴響應鏈來找到最終的響應者,在UIResponder的頭文件中可以看到吩屹,Touch events跪另,Motion events,Remote events煤搜,UIControl Action免绿,Text editing,press events等事件都可以在響應鏈中傳遞擦盾。

尋找響應對象

UIApplication在處理的事件時嘲驾,觸摸事件會交給hitTest view開始的響應鏈處理,其他的動作事件迹卢,遠程事件辽故,系統(tǒng)事件等,會交給first responder開始的響應鏈處理婶希。
UIKit會將用戶事件發(fā)送給理論上最合適的對象榕暇。所以當程序中的響應者要經(jīng)過很長的查找路徑時,這時就要考慮是否實現(xiàn)是否設計合理了喻杈。

UIKit first sends the event to the object that is best suited to handle the event. For touch events, that object is the hit-test view, and for other events, that object is the first responder

對于觸摸事件,hit-test視圖獲得了最先接受觸摸對象的機會狰晚,但如果它不能處理對應的觸摸事件筒饰,那么UIKit會沿著以hit-test開頭的響應鏈尋找能夠最終的響應者。


The responder chain on iOS
The responder chain on iOS

當找到響應者或已經(jīng)到鏈尾(UIApplication)仍不能處理壁晒,UIKit會停止查找瓷们,對于后者,對應的事件會被丟棄。

除了UIResponder對象谬晕,UIGestureRecognizerUIControl也可以響應觸摸事件碘裕,但它們參與觸摸事件響應的方式不同。

  1. UIGestureRecognizer在響應鏈中的位置取決于依附的視圖攒钳。
  2. UIControl參與響應的方式?jīng)Q定于其關(guān)聯(lián)的target帮孔。
    UIGestureRecognizer要先于視圖收到觸摸事件,但需要注意的是不撑,若該視圖也可以響應觸摸事件(實現(xiàn)了UITouch生命周期函數(shù))文兢,那么手勢對象并不會阻礙視圖的響應,雙方是同時響應的焕檬,只不過存在先后順序姆坚。
    UIGestureRecognizer與UIView的接觸事件的次序
    UIGestureRecognizer與UIView的接觸事件的次序

響應觸摸事件

當確定了響應鏈后,UIWindow會向hitTest View發(fā)送以下方法:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

這是UIResponder用于響應觸摸事件的方法实愚,這些鉤子方法的默認實現(xiàn)是向nextResponder轉(zhuǎn)發(fā)方法兼呵。
當觸摸事件在響應鏈上傳遞時,判斷當前UIResponder能否響應的條件是:其是否實現(xiàn)了touchesBegan方法腊敲。
在這些UITouches序列的生命周期方法中萍程,我們可以獲取對應UIEventUITouch,利用它們所提供的信息兔仰,進一步?jīng)Q定如何響應用戶的觸摸事件茫负。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市乎赴,隨后出現(xiàn)的幾起案子忍法,更是在濱河造成了極大的恐慌,老刑警劉巖榕吼,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饿序,死亡現(xiàn)場離奇詭異,居然都是意外死亡羹蚣,警方通過查閱死者的電腦和手機原探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顽素,“玉大人咽弦,你說我怎么就攤上這事⌒渤觯” “怎么了型型?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長全蝶。 經(jīng)常有香客問我闹蒜,道長寺枉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任绷落,我火速辦了婚禮姥闪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘砌烁。我一直安慰自己筐喳,他們只是感情好,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布往弓。 她就那樣靜靜地躺著疏唾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪函似。 梳的紋絲不亂的頭發(fā)上槐脏,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音撇寞,去河邊找鬼顿天。 笑死,一個胖子當著我的面吹牛蔑担,可吹牛的內(nèi)容都是我干的牌废。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼啤握,長吁一口氣:“原來是場噩夢啊……” “哼鸟缕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起排抬,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤懂从,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蹲蒲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體番甩,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年届搁,在試婚紗的時候發(fā)現(xiàn)自己被綠了缘薛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡卡睦,死狀恐怖宴胧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情么翰,我是刑警寧澤牺汤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站浩嫌,受9級特大地震影響檐迟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜码耐,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一追迟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骚腥,春花似錦敦间、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至契沫,卻和暖如春带猴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背懈万。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工拴清, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人会通。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓口予,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涕侈。 傳聞我的和親對象是個殘疾皇子沪停,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

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