事件響應(yīng)

前言:蘋果的官方文檔《Event Handling Guide for iOS》對事件處理做了非常詳盡清晰的解釋搓彻,建議大家仔細研讀

關(guān)于iOS的事件響應(yīng)機制網(wǎng)上講解文章不少,有的文章內(nèi)容少沒講全面佩谣,有的說的太多佩迟,一個概念反復(fù)說蚁鳖,傳遞和響應(yīng)混在一起講,不好理解婴噩,我綜合參考了幾篇文章總結(jié)了一下,覺得可以分為以下幾點來講

1. iOS中的事件介紹

2. 事件的產(chǎn)生和傳遞

3. 事件響應(yīng)

4. 實際項目中的應(yīng)用

1.iOS中的事件介紹

iOS中的事件可以分為3大類型:

觸屏事件(例如點擊按鈕羽德、通過手勢縮放圖片几莽、拖動上下滾動頁面等)

傳感器事件(例如搖一搖紅包、通過旋轉(zhuǎn)設(shè)備控制賽車方向宅静、指南針等)

遠程控制事件(例如耳機的線控章蚣、外接手柄、遙控器等)

著重講解一下iOS處理觸屏事件姨夹,觸屏事件分為兩種方式:

高級事件處理:利用UIKit提供的各種用戶控件或者手勢識別器來處理事件纤垂。

低級事件處理:在UIView的子類中重寫觸屏回調(diào)方法,直接處理觸屏事件磷账。

如果想監(jiān)聽一個view上面的觸摸事件峭沦,之前的做法是:

(1)自定義一個view。

(2)實現(xiàn)view的touches方法逃糟,在方法內(nèi)部實現(xiàn)具體處理代碼吼鱼。

通過touches方法監(jiān)聽view觸摸事件蓬豁,有很明顯的幾個缺點:

(1)必須得自定義view。

(2)由于是在view內(nèi)部的touches方法中監(jiān)聽觸摸事件菇肃,因此默認情況下地粪,無法讓其他外界對象監(jiān)聽view的觸摸事件。

(3)不容易區(qū)分用戶的具體手勢行為琐谤。

iOS 3.2之后蟆技,蘋果推出了手勢識別功能(Gesture Recognizer),在觸摸事件處理方面斗忌,大大簡化了開發(fā)者的開發(fā)難度付魔。

UIKit中我們常用的是UIControl類實例的addTarget:action:forControlEvents:方法維護控件目標行為表,除了UIKit控件外飞蹂,手勢識別器UIGestureRecognizer類的實例也是處理觸屏事件的好幫手几苍,其內(nèi)部也使用目標行為表。

為什么手勢和單擊事件只會響應(yīng)手勢陈哑?

UIGestureRecognizer 有個屬性cancelsTouchesInView,這個屬性默認值是YES妻坝,即當手勢識別成功后,會發(fā)送touchesCancelled消息給view來結(jié)束view的響應(yīng)惊窖。

如果cancelsTouchesInView為NO刽宪,那么gestureRecognizer和view都可以響應(yīng)

UIKit內(nèi)置了6種手勢識別器:

UITapGestureRecognizer:點擊(單擊、雙擊界酒、三連擊等)手勢圣拄。

UIPinchGestureRecognizer:縮放手勢。

UIPanGestureRecognizer:拖拽手勢毁欣。

UISwipeGestureRecognizer:滑動手勢庇谆。

UIRotationGestureRecognizer:旋轉(zhuǎn)手勢。

UILongPressGestureRecognizer:長按手勢凭疮。

UIKit控件和手勢識別器屬于高級事件處理的范疇饭耳,這些不再多說,以下文字都是介紹的低級事件處理過程#### UITouch當你用一根手指觸摸屏幕時, 會創(chuàng)建一個與之關(guān)聯(lián)的UITouch對象, 一個UITouch對象對應(yīng)一根手指. 在事件中可以根據(jù)NSSet中UITouch對象的數(shù)量得出此次觸摸事件是單指觸摸還是雙指多指等等

觸摸產(chǎn)生時所處的窗口

@property(nonatomic,readonly,retain) UIWindow *window;

觸摸產(chǎn)生時所處的視圖

@property(nonatomic,readonly,retain) UIView? *view;

短時間內(nèi)點按屏幕的次數(shù)执解,可以根據(jù)tapCount判斷單擊寞肖、雙擊或更多的點擊

@property(nonatomic,readonly) NSUInteger? ? ? tapCount;

記錄了觸摸事件產(chǎn)生或變化時的時間,單位是秒

@property(nonatomic,readonly) NSTimeInterval? timestamp;

當前觸摸事件所處的狀態(tài)

@property(nonatomic,readonly) UITouchPhase? ? phase;

#### UIEvent每產(chǎn)生一個事件, 就對應(yīng)產(chǎn)生一個UIEvent.UIEvent記錄著該事件產(chǎn)生的時間, 事件的類型等等UIEvent幾個重要的屬性 :

事件類型

@property(nonatomic,readonly) UIEventType? ? type;

@property(nonatomic,readonly) UIEventSubtype? subtype;

事件產(chǎn)生的時間

@property(nonatomic,readonly) NSTimeInterval? timestamp;

#### 響應(yīng)者對象(UIResponder)在iOS中不是任何對象都能處理事件, 只有繼承了UIResponder的對象才能接收并處理事件,我們稱為響應(yīng)者對象UIApplication,UIViewController,UIView都繼承自UIResponder,因此他們都是響應(yīng)者對象, 都能夠接收并處理事件繼承自UIResponder的類能處理事件是由于UIResponder內(nèi)部提供了以下方法

觸摸事件

(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;

傳感器事件

(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;

(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;

(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

遠程控制事件

(void)remoteControlReceivedWithEvent:(UIEvent *)event;

兩個UIView相關(guān)屬性:* multipleTouchEnabled:是否開啟多點觸控* exclusiveTouch :多個控件接受事件時的排他性---------------------------------------------------------------### 2. iOS中事件的產(chǎn)生和傳遞1.發(fā)生觸摸事件后衰腌,系統(tǒng)會將該事件加入到一個由UIApplication管理的隊列事件中2.UIApplication會從事件隊列中取出最前面的事件新蟆,并將事件分發(fā)下去以便處理,通常會先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)3.主窗口會在視圖層次結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸事件事件的具體傳遞過程右蕊,如圖:![150807180781051.png](http://upload-images.jianshu.io/upload_images/2591298-8692cfacbc2c9a54.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/240)一般事件的傳遞是從父控件傳遞到子控件的例如:點擊了綠色的View琼稻,傳遞過程如下:UIApplication->Window->白色View->綠色View點擊藍色的View,傳遞過程如下:UIApplication->Window->白色View->橙色View->藍色View如果父控件接受不到觸摸事件尤泽,那么子控件就不可能接收到觸摸事件### ( 重難點)如何尋找最合適的view

應(yīng)用如何找到最合適的控件來處理事件欣簇?有以下準則

1.首先判斷主窗口(keyWindow)自己是否能接受觸摸事件

2.觸摸點是否在自己身上

3.從后往前遍歷子控件,重復(fù)前面的兩個步驟(首先查找數(shù)組中最后一個元素)

4.如果沒有符合條件的子控件坯约,那么就認為自己最合適處理

詳述:

1.主窗口接收到應(yīng)用程序傳遞過來的事件后熊咽,首先判斷自己能否接手觸摸事件。如果能闹丐,那么在判斷觸摸點在不在窗口自己身上

2.如果觸摸點也在窗口身上横殴,那么窗口會從后往前遍歷自己的子控件(遍歷自己的子控件只是為了尋找出來最合適的view)

3.遍歷到每一個子控件后,又會重復(fù)上面的兩個步驟(傳遞事件給子控件卿拴,1.判斷子控件能否接受事件衫仑,2.點在不在子控件上)

4.如此循環(huán)遍歷子控件,直到找到最合適的view堕花,如果沒有更合適的子控件文狱,那么自己就成為最合適的view。

注意:之所以會采取從后往前遍歷子控件的方式尋找最合適的view只是為了做一些循環(huán)優(yōu)化缘挽。因為相比較之下瞄崇,后添加的view在上面,降低循環(huán)次數(shù)壕曼。

UIView不能接收觸摸事件的三種情況:* 不接受用戶交互:userInteractionEnabled =NO;* 隱藏:hidden =YES;* 透明:alpha =0.0~0.01尋找最合適的view過程苏研,如圖:![150807180781052.png](http://upload-images.jianshu.io/upload_images/2591298-106eb30915d918e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/240)說明一下控件的添加順序:白1->綠2->橙2->藍3->紅3->黃4這里點擊了橙色的那塊區(qū)域,事件傳遞判斷過程如下:1.UIApplication從事件隊列中取出事件分發(fā)給UIWindow2.UIWindow判斷自己是否能接受觸摸事件腮郊,可以3.UIWindow判斷觸摸點是否在自己身上摹蘑,是的。4.UIWindow從后往前便利自己的子控件轧飞,取出白15.白1都滿足最上面兩個條件衅鹿,遍歷子控件橙26.橙2都滿足最上面兩個條件,遍歷子控件过咬,先取出紅37.紅3不滿足條件2塘安,取出藍38.藍3也不滿足條件2,最后最合適的控件是橙2#### 在事件傳遞尋找最合適的View時援奢,底層到底干了哪些事兼犯?尋找合適的View用到兩個重要方法:*? hitTest:withEvent: *? pointInside####? hitTest:withEvent:方法什么時候調(diào)用?* 只要事件一傳遞給一個控件,這個控件就會調(diào)用他自己的hitTest:withEvent:方法尋找合適的View

作用

尋找并返回最合適的view(能夠響應(yīng)事件的那個最合適的view)

注 意:不管這個控件能不能處理事件集漾,也不管觸摸點在不在這個控件上切黔,

事件都會先傳遞給這個控件,隨后再調(diào)用hitTest:withEvent:方法

hitTest:withEvent:底層調(diào)用流程:![123.png](http://upload-images.jianshu.io/upload_images/2591298-d188efbf373a2a4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/540)

底層具體實現(xiàn)如下 :

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

{

// 1.判斷當前控件能否接收事件

if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;

// 2. 判斷點在不在當前控件

if ([self pointInside:point withEvent:event] == NO) return nil;

// 3.從后往前遍歷自己的子控件

NSInteger count = self.subviews.count;

for (NSInteger i = count - 1; i >= 0; i--) {

UIView *childView = self.subviews[i];

// 把當前控件上的坐標系轉(zhuǎn)換成子控件上的坐標系

CGPoint childP = [self convertPoint:point toView:childView];

UIView *fitView = [childView hitTest:childP withEvent:event];

if (fitView) { // 尋找到最合適的view

return fitView;

}

}

// 循環(huán)結(jié)束,表示沒有比自己更合適的view

return self;

}

**事件傳遞給窗口或控件的后具篇,就調(diào)用hitTest:withEvent:方法尋找更合適的view纬霞。所以是,先傳遞事件驱显,再根據(jù)事件在自己身上找更合適的view诗芜。不管子控件是不是最合適的view瞳抓,系統(tǒng)默認都要先把事件傳遞給子控件,經(jīng)過子控件調(diào)用自己的hitTest:withEvent:方法驗證后才知道有沒有更合適的view伏恐。即便父控件是最合適的view了孩哑,子控件的hitTest:withEvent:方法還是會調(diào)用,不然怎么知道有沒有更合適的翠桦!即横蜒,如果確定最終父控件是最合適的view,那么該父控件的子控件的hitTest:withEvent:方法也是會被調(diào)用的销凑。** hitTest:withEvent:方法忽略隱藏(hidden=YES)的視圖,禁止用戶操作(userInteractionEnabled=YES)的視圖丛晌,以及alpha級別小于0.01(alpha<0.01)的視圖。? ? ? 如果一個子視圖的區(qū)域超過父視圖的bound區(qū)域(父視圖的clipsToBounds 屬性為NO,這樣超過父視圖bound區(qū)域的子視圖內(nèi)容也會顯示)斗幼,那么正常情況下對子視圖在父視圖之外區(qū)域的觸摸操作不會被識別,因為父視圖的pointInside:withEvent:方法會返回NO,這樣就不會繼續(xù)向下遍歷子視圖了澎蛛。** - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event **該方法判斷觸摸點是否在控件身上, 是則返回YES, 否則返回NO**作用? 可以使用以上兩個方法做到:? 指鹿為馬(明明點擊的是B視圖, 卻由A視圖來響應(yīng)事件)? 穿透某控件點擊被覆蓋的下一層控件? 讓父控件frame之外的子控件響應(yīng)觸摸事件(下面實際應(yīng)用中有具體介紹)**? ? ? ---------------------------------------------------------------### 3.事件響應(yīng)上文介紹了事件的傳遞過程,找到合適的View之后就會調(diào)用該view的touches方法要進行響應(yīng)處理具體的事件蜕窿,找不到最合適的view瓶竭,就不會調(diào)用touches方法進行事件處理。這里先介紹一下響應(yīng)者鏈條:響應(yīng)者鏈條其實就是很多響應(yīng)者對象(繼承自UIResponder的對象)一起組合起來的鏈條稱之為響應(yīng)者鏈條一般默認做法是控件將事件順著響應(yīng)者鏈條向上傳遞渠羞,將事件交給上一個響應(yīng)者進行處理 (即調(diào)用super的touches方法)斤贰。那么如何判斷當前響應(yīng)者的上一個響應(yīng)者是誰呢?有以下兩個規(guī)則:1.判斷當前是否是控制器的View次询,如果是控制器的View荧恍,上一個響應(yīng)者就是控制器2.如果不是控制器的View,上一個響應(yīng)者就是父控件響應(yīng)過程如下圖:![150807180781053.png](http://upload-images.jianshu.io/upload_images/2591298-5c25578f2e8d950b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)touch響應(yīng):* 找到最合適的view會調(diào)用touches方法處理事件* touches默認做法是把事件順著響應(yīng)者鏈條向上拋

//只要點擊控件,就會調(diào)用touchBegin,如果沒有重寫這個方法,自己處理不了觸摸事件

// 上一個響應(yīng)者可能是父控件

(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{

// 默認會把事件傳遞給上一個響應(yīng)者,上一個響應(yīng)者是父控件,交給父控件處理

[super touchesBegan:touches withEvent:event];

// 注意不是調(diào)用父控件的touches方法屯吊,而是調(diào)用父類的touches方法

// super是父類 superview是父控件

}

如果控制器也不響應(yīng)響應(yīng)touches方法送巡,就交給UIWindow。如果UIWindow也不響應(yīng)盒卸,交給UIApplication骗爆,如果都不響應(yīng)事件就作廢了。用具體的例子來看下響應(yīng)過程蔽介,例子太簡單就不上demo了摘投,看下截圖(3-2)![c50b227db9693bf974cf4f9e01eb9a98.jpg](http://upload-images.jianshu.io/upload_images/2591298-7b0ba27676160e08.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/340)(圖3-2)? 控件的添加順序:紅1->藍2->綠2->黃3![屏幕快照2016-08-28下午1.56.37.png](http://upload-images.jianshu.io/upload_images/2591298-041859d799cf9101.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/740)綠色View實現(xiàn)了touch方法,如下![屏幕快照2016-08-28下午1.58.41.png](http://upload-images.jianshu.io/upload_images/2591298-d4fbf11a7f05de63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/740)黃色view沒有實現(xiàn)touch方法虹蓄,如下![屏幕快照2016-08-28下午1.58.52.png](http://upload-images.jianshu.io/upload_images/2591298-6b8902d4a995f9b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/740)當點擊黃色區(qū)域時犀呼,由于黃色view沒有實現(xiàn)touch方法,就順著響應(yīng)鏈找到其父view(綠色View)薇组,綠色view實現(xiàn)了touch方法外臂,便打印了-- touchGreen最后總結(jié)來說一次完整的觸摸事件的傳遞響應(yīng)過程為:UIApplication-->UIWindow-->遞歸找到最合適處理的控件-->控件調(diào)用touches方法-->判斷是否實現(xiàn)touches方法-->沒有實現(xiàn)默認會將事件傳遞給上一個響應(yīng)者-->找到上一個響應(yīng)者-->找不到方法作廢一句話總結(jié)整個過程是:觸摸或者點擊一個控件,然后這個事件會從上向下(從父->子)找最合適的view處理律胀,找到這個view之后看他能不能處理宋光,能就處理貌矿,不能就按照事件響應(yīng)鏈向上(從子->父)傳遞給父控件事件的傳遞和響應(yīng)的區(qū)別:事件的傳遞是從上到下(父控件到子控件),事件的響應(yīng)是從下到上(順著響應(yīng)者鏈條向上傳遞:子控件到父控件罪佳。### 4 實際項目中的應(yīng)用* 情景1: 點擊子控件逛漫,讓父控件響應(yīng)事件;(點擊綠色View菇民,紅色View響應(yīng))分析:可通過兩種方式實現(xiàn)(1)因為hitTest:withEvent:方法的作用就是控件接收到事件后,判斷自己是否能處理事件投储,判斷點在不在自己的坐標系上第练,然后返回最合適的view。所以玛荞,我們可以在hitTest:withEvent:方法里面強制返回父控件為最合適的view.

作者:舊飯盆mingzhi_liu

鏈接:http://www.reibang.com/p/f55b613b564e

來源:簡書

著作權(quán)歸作者所有娇掏。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處勋眯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婴梧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子客蹋,更是在濱河造成了極大的恐慌塞蹭,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讶坯,死亡現(xiàn)場離奇詭異番电,居然都是意外死亡,警方通過查閱死者的電腦和手機辆琅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門漱办,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人婉烟,你說我怎么就攤上這事娩井。” “怎么了似袁?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵洞辣,是天一觀的道長。 經(jīng)常有香客問我昙衅,道長屋彪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任绒尊,我火速辦了婚禮畜挥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婴谱。我一直安慰自己蟹但,他們只是感情好躯泰,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著华糖,像睡著了一般麦向。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上客叉,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天诵竭,我揣著相機與錄音,去河邊找鬼兼搏。 笑死卵慰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的佛呻。 我是一名探鬼主播裳朋,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吓著!你這毒婦竟也來了鲤嫡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤绑莺,失蹤者是張志新(化名)和其女友劉穎暖眼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纺裁,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡罢荡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了对扶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片区赵。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浪南,靈堂內(nèi)的尸體忽然破棺而出笼才,到底是詐尸還是另有隱情,我是刑警寧澤络凿,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布骡送,位于F島的核電站,受9級特大地震影響絮记,放射性物質(zhì)發(fā)生泄漏摔踱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一怨愤、第九天 我趴在偏房一處隱蔽的房頂上張望派敷。 院中可真熱鬧,春花似錦、人聲如沸篮愉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽试躏。三九已至猪勇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颠蕴,已是汗流浹背泣刹。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留犀被,地道東北人椅您。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像弱判,于是被迫代替她去往敵國和親襟沮。 傳聞我的和親對象是個殘疾皇子锥惋,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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