iOS 中的事件的產(chǎn)生和傳遞

image.png

事件的產(chǎn)生

  • 當(dāng)有觸摸或者其他事件產(chǎn)生尊惰,將事件交由 IOKit.framework 處理。
  • IOKit.framework 將事件封裝成一個 IOHIDEvent 對象,并通過 mach port 傳遞給 SpringBoad面徽。
  • SpringBoard 會接收這個對象并通過 mach port 轉(zhuǎn)發(fā)給當(dāng)前 App 的進程腻要;
  • 喚醒 runloop复罐,觸發(fā)了 source1 回調(diào),其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()雄家。
  • source1 回調(diào)觸發(fā) source0 回調(diào)效诅,將接收到的 IOHIDEvent 對象封裝成 UIEvent 對象進行處理或分發(fā)。

注意:
SpringBoard 其實是一個標準的應(yīng)用程序趟济,這個應(yīng)用程序用來管理 iOS 的主屏幕乱投;
source1 是蘋果用來監(jiān)聽 mach port 傳來的系統(tǒng)事件的,source0 是用來處理用戶事件的顷编。
source1 收到系統(tǒng)事件后戚炫,會回調(diào) source0,所以最終這些事件都是由 source0 處理的媳纬。

事件傳遞的流程

  • 當(dāng)用戶點擊屏幕時双肤,會產(chǎn)生一個觸摸事件,系統(tǒng)會將該事件加入到一個由 UIApplication 管理的事件隊列中钮惠。
  • UIApplication 會從事件隊列中取出最前面的事件茅糜,并將事件分發(fā)下去以便處理,通常先發(fā)送事件給應(yīng)用程序的主窗口 keyWindow素挽。
  • 主窗口會調(diào)用 hitTest:withEvent: 方法在視圖 View 層次結(jié)構(gòu)中找到一個最合適的 View 來處理觸摸事件限匣。
  • 最終,這個觸摸事件交給主窗口的 hitTest:withEvent: 方法返回的視圖對象去處理毁菱。

注意:如果父控件不能接受觸摸事件,那么子控件就不可能接收到觸摸事件锌历。

View 不能接收觸摸事件的三種情況:

  • 不允許交互:userInteractionEnabled = NO贮庞;
  • 隱藏:如果把父控件隱藏,那么子控件也會隱藏究西,隱藏的控件不能接受事件窗慎;
  • 透明度:如果設(shè)置一個控件的透明度<0.01,會直接影響子控件的透明度卤材,0.0~0.01 為透明遮斥。

注意:
默認 UIImageView 不能接受觸摸事件,因為不允許交互扇丛,即 userInteractionEnabled = NO术吗。所以如果希望 UIImageView 可以交互,需要設(shè)置 UIImageViewuserInteractionEnabled = YES帆精。

如何找到最合適的控件來處理事件较屿?

  • 首先判斷主窗口 keyWindow 自己是否能接受觸摸事件隧魄。
  • 調(diào)用當(dāng)前視圖的 pointInside:withEvent: 方法判斷觸摸點是否在當(dāng)前視圖內(nèi)。
  • pointInside:withEvent: 方法返回 NO隘蝎,說明觸摸點不在當(dāng)前視圖內(nèi)购啄,則當(dāng)前視圖的 hitTest:withEvent: 返回 nil
  • pointInside:withEvent: 方法返回 YES嘱么,說明觸摸點在當(dāng)前視圖內(nèi)狮含,則遍歷當(dāng)前視圖的所有子視圖 subviews,調(diào)用子視圖的 hitTest:withEvent: 方法重復(fù)前面的步驟曼振,子視圖的遍歷順序是從上到下几迄,即從 subviews 數(shù)組的末尾向前遍歷,直到有子視圖的 hitTest:withEvent: 方法返回非空對象或者全部子視圖遍歷完畢拴测。
  • 若第一次有子視圖的 hitTest:withEvent: 方法返回非空對象乓旗,則當(dāng)前視圖的 hitTest:withEvent: 方法就返回此對象,處理結(jié)束集索。
  • 若所有子視圖的 hitTest:withEvent: 方法都返回 nil屿愚,則當(dāng)前視圖的 hitTest:withEvent: 方法返回當(dāng)前視圖自身。

查找第一響應(yīng)者

hitTest:withEvent:方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

控件通過重寫 hitTest:withEvent: 方法务荆,來判斷點擊區(qū)域是否在視圖上妆距,是則返回 YES,不是則返回 NO函匕,尋找并返回最合適的 view (能夠響應(yīng)事件的那個最合適的 view)。

應(yīng)用程序接收到事件后盅惜,將事件交給 keyWindow 并轉(zhuǎn)發(fā)給根視圖,根視圖按照視圖層級逐級遍歷子視圖结啼,并且遍歷的過程中不斷判斷視圖范圍,并最終找到第一響應(yīng)者屈芜。

事件傳遞給窗口或控件的后郊愧,就遞歸調(diào)用 hitTest:withEvent: 方法尋找更合適的 view井佑。
hitTest:withEvent: 方法中,會從上到下遍歷子視圖躬翁,并調(diào)用 subViewspointInside:withEvent: 方法焦蘑,通過重寫 pointInside:withEvent: 方法,返回點擊區(qū)域是否在視圖上盒发。如果找到子視圖則不斷調(diào)用其 hitTest:withEvent: 方法喇肋,以此類推。

在 hitTest:withEvent: 方法中返回 nil 的含義:

hitTest:withEvent: 方法中返回 nil 的意思是調(diào)用當(dāng)前 hitTest:withEvent: 方法的 view 不是合適的 view甚侣,子控件也不是合適的 view间学,如果同級的兄弟控件也沒有合適的 view,那么最合適的 view 就是父控件低葫。

pointInside:withEvent: 方法

pointInside:withEvent: 方法判斷子控件的點在不在當(dāng)前 view 上(方法調(diào)用者的坐標系上)如果返回 YES,代表點在方法調(diào)用者的坐標系上嘿悬;返回 NO 代表點不在方法調(diào)用者的坐標系上,那么方法調(diào)用者也就不能處理事件窒盐。

查找第一響應(yīng)者傳遞過程:

  • 如果當(dāng)前 view 是控制器的 view钢拧,那么控制器就是上一個響應(yīng)者,事件就傳遞給控制器源内;
  • 如果當(dāng)前 view 不是控制器的 view,那么父視圖就是當(dāng)前 view 的上一個響應(yīng)者嗽交,事件就傳遞給它的父視圖颂斜。
  • 在視圖層次結(jié)構(gòu)的最頂級視圖,如果也不能處理收到的事件或消息焚鲜,則其將事件或消息傳遞給 window 對象進行處理放前。
  • 如果 window 對象也不處理,則其將事件或消息傳遞給 UIApplication 對象凭语。
  • 如果 UIApplication 也不能處理該事件或消息葱她,則將其丟棄似扔。

事件攔截

有時候想讓指定視圖來響應(yīng)事件搓谆,不再向其子視圖繼續(xù)傳遞事件豪墅,可以通過重寫 hitTest:withEvent: 方法。在執(zhí)行到方法后斩萌,直接將該視圖返回屏轰,而不再繼續(xù)遍歷子視圖,這樣響應(yīng)者鏈的終端就是當(dāng)前視圖霎苗。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return self;
}

實際開發(fā)中可能會遇到一些特殊的交互需求,需要定制視圖對于事件的響應(yīng)内狸。例如下面 Tabbar 的這種情況升敲,中間的圓形按鈕是底部 Tabbar上的控件,而 Tabbar 是添加在控制器根視圖中的驴党。默認情況下我們點擊圖中紅色方框中按鈕的區(qū)域,會發(fā)現(xiàn)按鈕并不會得到響應(yīng)倔既。

image.png

很明顯鹏氧,圖中紅色方框中按鈕是添加在 Tabbar 上面的,但是圖中紅色方框中按鈕的位置又超出了 Tabbar 的區(qū)域实蓬,當(dāng)點擊紅色方框區(qū)域后吊履,會發(fā)現(xiàn)紅色方框得不到響應(yīng)。

分析:

  • 生成的觸摸事件首先傳到了 UIWindow酌伊,然后 UIWindow 將事件傳遞給控制器的根視圖 UILayoutContainerView缀踪;
  • UILayoutContainerView 判斷自己可以響應(yīng)觸摸事件虹脯,然后將事件傳遞給子視圖 Tabbar奏候;
  • 子視圖 Tabbar 判斷觸摸點并不在自己的坐標范圍內(nèi),因此返回 nil鼻由;
  • 這時 UILayoutContainerView 將事件傳遞其他子視圖 UINavigationTransitionViewUINavigationTransitionView 判斷自己可以響應(yīng)事件蔼紧,就將事件時間傳遞給其子視圖 UIViewControllerWrapperView狠轻;
  • UIViewControllerWrapperView 判斷自己可以響應(yīng)事件,就將事件傳遞給子視圖 UITableViewController 控制器的 TableView查吊;
  • TableView 判斷自己可以響應(yīng)事件湖蜕,所以 UITableViewController 控制器的 TableView 就是第一響應(yīng)者;

整個過程昭抒,事件根本沒有傳遞到圖中紅色方框中按鈕;

因此我們需要做的就是修改 TabbarhitTest:withEvent: 函數(shù)里面判斷點擊位置是否在 Tabbar 坐標范圍的的判斷條件盗迟,也就是需要重寫 Tabbar
pointInside:withEvent: 方法熙含,判斷如果當(dāng)前觸摸坐標在圖中紅色方框中按鈕上面,就返回 YES邮弹,否則返回 NO蚓聘;這樣一來時間就會最終傳遞到圖中紅色方框中按鈕上面,來響應(yīng)事件或粮。

// TabBar
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // 將觸摸點坐標轉(zhuǎn)換到在 circleButton 上的坐標
    CGPoint pointTemp = [self convertPoint:point toView:_circleButton];
    // 若觸摸點在 cricleButton 上則返回 YES
    if ([_circleButton pointInside:pointTemp withEvent:event]) {
        return YES;
    }
    // 否則返回默認的操作
    return [super pointInside:point withEvent:event];
}

事件轉(zhuǎn)發(fā)

在開發(fā)過程中氯材,經(jīng)常會遇到子視圖顯示范圍超出父視圖的情況,這時候可以重寫該視圖的 pointInside:withEvent: 方法氢哮,將點擊區(qū)域擴大到能夠覆蓋所有子視圖。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) return nil;
    
    CGFloat inset = 45.0f - 78.0f;
    CGRect touchRect = CGRectInset(self.bounds, inset, inset);
    
    if (CGRectContainsPoint(touchRect, point)) {
        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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市裂七,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腰吟,老刑警劉巖徙瓶,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異灵疮,居然都是意外死亡壳繁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門伍派,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剩胁,“玉大人,你說我怎么就攤上這事昵观。” “怎么了灼擂?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵觉至,是天一觀的道長。 經(jīng)常有香客問我峻贮,道長,這世上最難降的妖魔是什么纤控? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮刻撒,結(jié)果婚禮上耿导,老公的妹妹穿的比我還像新娘。我一直安慰自己碎节,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布胎撇。 她就那樣靜靜地躺著殖氏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爵憎。 梳的紋絲不亂的頭發(fā)上婚瓜,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音愚铡,去河邊找鬼胡陪。 笑死,一個胖子當(dāng)著我的面吹牛柠座,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淮野,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鳄厌!你這毒婦竟也來了妈踊?” 一聲冷哼從身側(cè)響起泪漂,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萝勤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慎式,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡趟径,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年蜗巧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幕屹。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡望拖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出说敏,到底是詐尸還是另有隱情,我是刑警寧澤锌雀,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布迅诬,位于F島的核電站,受9級特大地震影響侈贷,放射性物質(zhì)發(fā)生泄漏等脂。R本人自食惡果不足惜撑蚌,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一争涌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亮垫,春花似錦、人聲如沸饮潦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稀并。三九已至,卻和暖如春灵莲,著一層夾襖步出監(jiān)牢的瞬間殴俱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工线欲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苦锨。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓趴泌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秃励。 傳聞我的和親對象是個殘疾皇子吉捶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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

  • 一皆尔、概念(載錄于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434閱讀 8,373評論 6 152
  • Http協(xié)議詳解 標簽(空格分隔): Linux 聲明:本片文章非原創(chuàng)慷蠕,內(nèi)容來源于博客園作者MIN飛翔的HTTP協(xié)...
    Sivin閱讀 5,226評論 3 82
  • http協(xié)議有http0.9食呻,http1.0,http1.1和http2三個版本仅胞,但是現(xiàn)在瀏覽器使用的是htt...
    一現(xiàn)_閱讀 1,866評論 0 3
  • HTTP是一個屬于應(yīng)用層的面向?qū)ο蟮膮f(xié)議莱革,由于其簡捷、快速的方式盅视,適用于分布式超媒體信息系統(tǒng)旦万。它于1990年提出,...
    greenlift閱讀 1,994評論 0 7
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理赏半,服務(wù)發(fā)現(xiàn),斷路器断箫,智...
    卡卡羅2017閱讀 134,708評論 18 139