iOS 觸摸事件的處理層次及原理

iOS 事件包括:運(yùn)動(dòng)事件枚钓、遠(yuǎn)程控制事件电湘、觸摸事件。

其中觸摸事件的響應(yīng)流程是:當(dāng)手指觸摸屏幕時(shí)值纱,會(huì)產(chǎn)生一個(gè)事件,事件發(fā)生會(huì)被系統(tǒng)封裝為一個(gè) IOHIDEvent對(duì)象坯汤,傳遞給 SpringBoard(桌面)虐唠,然后將這個(gè)桌面進(jìn)程傳遞給當(dāng)前 app。app 的主線程 Runloop 會(huì)被觸發(fā)惰聂,因此會(huì)觸發(fā) Source1回調(diào)疆偿,Source1回調(diào)內(nèi)部觸發(fā) Source0 回調(diào),然后傳遞給 UIWindow庶近,app 內(nèi)部開(kāi)始傳遞和響應(yīng)翁脆。

事件傳遞與響應(yīng)

事件傳遞流程:通過(guò)hitTestpointInside來(lái)反序遍歷找到正確的view鼻种。
從UIWindow開(kāi)始反番,反向遍歷 UIWindow 的 subviews。從最后一個(gè) subview 開(kāi)始,判斷點(diǎn)擊區(qū)域是不是在當(dāng)前 view 的 bounds 范圍內(nèi)罢缸。如果是(pointInside 返回 YES)篙贸,反向遍歷當(dāng)前 view 的 subviews,如果沒(méi)有 subviews枫疆,則當(dāng)前 view 是需要的 view爵川;如果不是(pointInside 返回 YES), 查找當(dāng)前 view 的兄弟 view息楔。這是一個(gè)遞歸查找過(guò)程寝贡。

pointInside:

  • 如果返回 NO,則不會(huì)遍歷該 view 的 subviews值依;
  • 觸摸的點(diǎn)不在范圍內(nèi)圃泡,找兄弟級(jí)別,在范圍內(nèi)愿险,找兒子級(jí)別颇蜡。

hitTest:

  • 會(huì)調(diào)用pointInside
  • 根據(jù)pointInside的值做不同的處理:如果pointInside為 YES辆亏,則反序遍歷 subviews风秤;如果不是,返回 nil扮叨。
  • 如果點(diǎn)擊區(qū)域在自己這里缤弦,而不是在子 view 里面,返回自己甫匹;
  • 如果點(diǎn)擊區(qū)域在子 view 里甸鸟,返回子 view。

注意:如果 view 的alpha 小于等于0.01兵迅,或者 userInteractionEnabled 屬性為 NO抢韭,或者 hidden 為 YES 時(shí),view 不響應(yīng)事件恍箭。

事件響應(yīng):

每個(gè)view都有一個(gè)nextResponder對(duì)象刻恭,這樣就逐級(jí)串聯(lián)起一個(gè)響應(yīng)鏈。

有了響應(yīng)鏈扯夭,會(huì)進(jìn)行touch四個(gè)方法的傳遞響應(yīng)鳍贾;可以通過(guò)不寫(xiě) super touchBegin/moved/cancel/end 來(lái)阻止傳遞流程。
scrollView的 touch 事件傳遞不了交洗,因?yàn)橄到y(tǒng)會(huì)重寫(xiě) scrollView 的 super touch:骑科,導(dǎo)致無(wú)法向上傳遞。

手勢(shì)

手勢(shì)和 pointInside 及 hitTest 的關(guān)系:

  • 必須通過(guò) pointInside 和 hitTest 找到 view构拳,才能響應(yīng) view 的手勢(shì)事件咆爽;

  • 通過(guò) pointInside 梁棠、 hitTest 找到的 view,其自身和它的 superview 的手勢(shì)都能響應(yīng)斗埂,但不會(huì)響應(yīng)其子 view 的手勢(shì)符糊;

  • 手勢(shì)的種類(lèi)根據(jù)收拾自己的 touchs 的四個(gè)方向辨別;

  • 默認(rèn)情況下呛凶,如果手勢(shì)識(shí)別出來(lái)了男娄,會(huì) cancel 掉 view 的 touch 事件。

  • 修改 delaysContentTouches canCancelContentTouches 的值可使手勢(shì)和 touch 共存漾稀。
    scrollView.delaysContentTouches = NO; // 如果為 YES模闲,阻止 touch 方法的識(shí)別
    scrollView.canCancelContentTouches = NO;
    這兩個(gè)屬性可實(shí)現(xiàn)scrollView上的其他view滑動(dòng)時(shí),scrollView不滑動(dòng)

button 事件

  • button 和 pointInside 及 hitTest 的關(guān)系:hitTest 必須有返回的 button县好,才能響應(yīng)事件围橡。
  • button 不同事件的識(shí)別也是通過(guò)四個(gè) touch 方法分辨的。
  • button 的 sendActionForControlEvents: 方法可以識(shí)別事件缕贡。

如果想讓 button 的響應(yīng)區(qū)域變大,只需要在想要的區(qū)域里拣播,pointInside 返回為 YES 就可以晾咪。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    CGRect rect = self.bounds;
    
    if (rect.size.width < 50.f) {
        rect.origin.x -= (50.f - rect.size.width) / 2;
    }
    if (rect.size.height < 50.f) {
        rect.origin.y -= (50.f - rect.size.height) / 2;
    }
    
    rect.size.width = 50.f;
    rect.size.height = 50.f;
    
    if (CGRectContainsPoint(rect, point)) {
        return YES;
    }
    
    return [super pointInside:point withEvent:event];
}

如果一個(gè) button 超出了其父控件的范圍,要讓超出范圍的區(qū)域響應(yīng) button 的點(diǎn)擊贮配,則需要在其父類(lèi)的pointInside方法里判斷谍倦。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    UIView *subview = self.subviews[0];
    CGPoint convertedPoint = [self convertPoint:point toView:subview];
    if ([subview pointInside:convertedPoint withEvent:event]) {
        return YES;
    }
    
    return [super pointInside:point withEvent:event];
}

手勢(shì)的互斥與共存

  1. 通過(guò) UIGestureRecognizerDelegate 的代理方法操作
/**
 手勢(shì)已經(jīng)識(shí)別,通過(guò)返回值確定是否響應(yīng)
 */
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    return YES;
}

/**
 兩個(gè)手勢(shì)是否共存泪勒,只要其中一個(gè)手勢(shì)的代理d方法返回 YES昼蛀,則兩個(gè)手勢(shì)共存
 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return NO;
}

/**
 gestureRecognizer 要響應(yīng),必須 otherGestureRecognizer 響應(yīng)失敗圆存,otherGestureRecognizer 優(yōu)先級(jí)高
 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}
/**
 otherGestureRecognizer 要響應(yīng)叼旋,必須 gestureRecognizer 響應(yīng)失敗,gestureRecognizer 優(yōu)先級(jí)高
 */
//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
//    return YES;
//}

/**
 手勢(shì)是否支持 touch
 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    return YES;
}
  1. 讓其中一個(gè)手勢(shì)的優(yōu)先級(jí)變高A requireGestureRecognizerToFail:B(讓 B 優(yōu)先級(jí)更高)沦辙。

UITableView 的 didselect 方法是通過(guò) touch 起作用夫植,如果給 tableview 的父控件添加手勢(shì),則 tableview 的 didselect 方法不會(huì)實(shí)現(xiàn)油讯。原因是 tableview 的 touch 事件和父控件的手勢(shì)沖突详民,根本原因是手勢(shì)的 cancelsTouchesInView 的值為 YES。

cancelsTouchesInView 的作用是當(dāng)手勢(shì)識(shí)別出來(lái)時(shí)陌兑,會(huì)取消touch事件沈跨。

解決以上實(shí)例的途徑是將手勢(shì)的 cancelsTouchesInView 的值設(shè)為 NO。此時(shí)手勢(shì)和 tableview 的 touch 都會(huì)響應(yīng)兔综。

要 didselect 方法起作用而手勢(shì)不響應(yīng)饿凛,應(yīng)該在手勢(shì)的代理方法 shouldReceiveTouch 中判斷 touch 的 view 的種類(lèi)隅俘。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    UIView *view = touch.view;
    return ![view isKindOfClass:NSClassFromString(@"UITableViewCellContentView")];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市笤喳,隨后出現(xiàn)的幾起案子为居,更是在濱河造成了極大的恐慌,老刑警劉巖杀狡,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙畴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡呜象,警方通過(guò)查閱死者的電腦和手機(jī)膳凝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恭陡,“玉大人蹬音,你說(shuō)我怎么就攤上這事⌒萃妫” “怎么了著淆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拴疤。 經(jīng)常有香客問(wèn)我永部,道長(zhǎng),這世上最難降的妖魔是什么呐矾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任苔埋,我火速辦了婚禮,結(jié)果婚禮上蜒犯,老公的妹妹穿的比我還像新娘组橄。我一直安慰自己,他們只是感情好罚随,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布玉工。 她就那樣靜靜地躺著,像睡著了一般毫炉。 火紅的嫁衣襯著肌膚如雪瓮栗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,578評(píng)論 1 305
  • 那天瞄勾,我揣著相機(jī)與錄音费奸,去河邊找鬼。 笑死进陡,一個(gè)胖子當(dāng)著我的面吹牛愿阐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趾疚,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缨历,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼以蕴!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起辛孵,我...
    開(kāi)封第一講書(shū)人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丛肮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后魄缚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宝与,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年冶匹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了习劫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嚼隘,死狀恐怖诽里,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情飞蛹,我是刑警寧澤谤狡,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站桩皿,受9級(jí)特大地震影響豌汇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泄隔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宛徊。 院中可真熱鬧佛嬉,春花似錦、人聲如沸闸天。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)苞氮。三九已至湾揽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笼吟,已是汗流浹背库物。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贷帮,地道東北人戚揭。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像撵枢,于是被迫代替她去往敵國(guó)和親民晒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子精居,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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