事件層次分析 和 手勢.scrollView.control跟響應鏈的關系 講解


值得注意的事,當一個view上面有多個手勢時,touch是無序的



1事件是怎么樣產生與傳遞的?(由上至下的過程)

當發生一個觸摸事件后,系統會將該事件加入到一個由UIApplication管理的事件隊列中.

UIApplication會從事件隊列中取出最前面的事件,并將事件分發下去以便處理.

主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件

觸摸事件的傳遞是從父控件傳遞到子控件的.

如果一個父控件不能接收事件,那么它里面的了子控件也不能夠接收事件.


如何尋找最合適的View?

主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件.

那如何找到最合適的View呢?

1.先判斷自己是否能夠接收觸摸事件,如果能再繼續往下判斷,

2.再判斷觸摸的當前點在不在自己的身上.

3.如果在自己身上,它會從后往前遍歷子控件,遍歷出每一個子控件后,重復前面的兩個步驟.

4.如果沒有符合條件的子控件,那么它自己就是最適合的View.




2-事件響應(由下至上)

用戶點擊屏幕后產生的一個觸摸事件,經過一系列的傳遞過程后,會找到最合適的視圖控件來處理這個事件,

找到最合適的視圖控件后,就會調用控件的touches方法來作具體的事件處理

那這些touches方法的默認做法是將事件順著響應者鏈條向上傳遞,將事件交給上一個響應者進行處理

什么是響應者鏈條?

是由多個響應者對象連接起來的鏈條.

什么是響應者對象?

繼承了UIResponder對象我們稱之為響應者對象,也就是能處理事件的對象

事件傳遞的完整過程?

在產生一個事件時,系統會將該事件加入到一個由UIApplication管理的事件隊列中,

UIApplication會從事件隊列中取出最前面的事件,將它傳遞給先發送事件給應用程序的主窗口.

主窗口會調用hitTest方法尋找最適合的視圖控件,找到后就會調用視圖控件的touches方法來做具體的事情.

當調用touches方法,它的默認做法, 就會將事件順著響應者鏈條往上傳遞,

傳遞給上一個響應者,接著就會調用上一個響應者的touches方法

如何去尋找上一個響應者?

1.如果當前的View是控制器的View,那么控制器就是上一個響應者.

2.如果當前的View不是控制器的View,那么它的父控件就是上一個響應者.

3.在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理

4.如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象

5.如果UIApplication也不能處理該事件或消息,則將其丟棄

3事件的產生與傳遞(由上至下)? ? 和? ? 事件響應(由下至上)

(通俗講: 方便大家理解,并無其他意思,勿噴.)

第一步? 事件的傳遞與傳遞 ? (找到哪里發生地點)

一個地方發生了大型搶劫時間.

國家知道了有這個事件,就先省份上找,并按照省份名單順序, 國家問浙江: 浙江浙江,是你那邊的事嗎?? 浙江回答:不是.? ? 再問福建:

福建,是你那邊的事嗎?? 福建回答:是的.? 然后福建在問它下面的城市,以此類推,一直找到 最終的地方.

第二步 事件響應 (把事件交給合適的部門處理)

發生的地點把 這個事件, 交給了這個地點的部門,?? 地點的部門說對不起,我處理不了.??

地點部門交給了? 市部門,市部門收到事件,判斷能否處理(能處理則不傳遞上去,不能則傳給上級部門),以此類推,一直傳到國家(如果連國家都不能處理則 丟棄這個事情)

補充

處理部門判斷能否處理? 的標準是? 1手勢的響應2繼承于UIControl都會阻斷 響應者鏈條的往上傳遞(button繼承于UIControl)3繼承于UIScroll也會阻斷往上傳遞

即總的來說:能響應事件的對象通常默認取消事件往上傳遞(都有部門接收處理這個事件了,當然不用往上傳了,哪怕像button scroll這種流氓部門 不管有對這個事件只接收不處理,它都打斷了往上傳遞)










3-hitTest方法與PointInside方法

個人通俗理解
1順序:先執行hitTest 再PointInside?

2 hitTest作用:尋找個合適的View,先遍歷subView尋找,找到則返回view,找不到返回self

PointInside判斷點擊的點 是否在這個View上

作用:尋找最適合的View

參數:當前手指所在的點.產生的事件

返回值:返回誰, 誰就是最適合的View.

只要一個事件,傳遞給一個控件時, 就會調用這個控件的hitTest方法

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

作用:判斷point在不在方法調用者上

point:必須是方法調用者的坐標系

hitTest方法底層會調用這個方法,判斷點在不在控件上.

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{

return YES;

}

hitTest底層實現:

1.判斷當前能不能接收事件

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

return? nil;

2.判斷觸摸點在不在當前的控件上

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

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

int count = (int)self.subviews.count;

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

UIView *childV = self.subviews[i];

把當前坐標系上的點轉換成子控件坐標系上的點.

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

判斷自己的子控件是不是最適合的View

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

如果子控件是最合適的View,直接返回

if (fitView) {

return? fitView;

}

}


(下圖代碼可以看出 hitTest里面 執行pointInSide)



4.自己就是最適合的View

return self.



4一個控件什么情況下不能夠接收事件.

1.不接收用戶交互時不能夠處理事件

userInteractionEnabled = NO

2.當一個控件隱藏的時候不能夠接收事件

Hidden = YES的時候

3.當一個控件為透明白時候也不能夠接收事件

注意:UIImageView的userInteractionEnabled默認就是NO,

因此UIImageView以及它的子控件默認是不能接收觸摸事件的

5響應鏈的實際運用

1放大button的作用域(超過它的frame仍能響應)

2點擊的view,卻讓其他view響應


6-hitTest練習1

業務邏輯:

底部一個按鈕, 按鈕的上面有一個View,遮擋在按鈕的上面.

點擊View時, View接收事件,當發現點擊的點在按鈕的位置時, 讓底部的按鈕處理事件.

實現思路:

實現View的touchBegain方法,先堅聽UIView的點擊.

并去實現UIView的HitTest方法, 在hitTest方法當中通過把當前點轉換成按鈕所在的坐標系

CGPoint btnP = [self convertPoint:point toView:self.btn];

轉換過后查看當前點在不在按鈕上,如果在按鈕上,就直接返回按鈕.

如果有在按鈕上,保持系統默認做法.

實現代碼:

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

判斷當前點在不在按鈕上.

把當前點轉換成按鈕所在的坐標系

CGPoint btnP = [self convertPoint:point toView:self.btn];

if ([self.btn pointInside:btnP withEvent:event]) {

return self.btn;

}else{

return [super hitTest:point withEvent:event];

}

}


7-hitTest方法練習2

業務邏輯:

按鈕可以隨著手指拖動而拖動.拖動過程當中,按鈕當中的子控件也跟著拖動.

讓超過按鈕的子控件也能夠響應事件,一般情況下,當一個控件超過他的父控件的時候,是不能夠接收事件的.

現在要做的事情就讓超過父控件的按鈕也能夠響應事件.

實現思路:

先辦到讓按鈕能夠跟隨著手指移動而移動.

實現按鈕的touchesMoved方法,在touchesMoved方法當中,獲得當前手指所在的點.以前上一個點.

分別計算X軸的偏移量以及Y軸的偏移量.

然后修改當前按鈕的transform讓按鈕辦到能夠跟隨著手指移動而移動.

第二步, 實現按鈕的hitTest方法.

在該方法當中去判斷當前的點在不在按鈕的子控件上.

如果在按鈕的子控件上.就返回按鈕的子控件如果不在的話, 就保持系統的默認做法.

實現代碼:

第一步,讓按鈕能夠跟隨著手指移動而移動

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{

獲取當前的手指

UITouch *touch = [touches anyObject];

獲取當前手指所在的點

CGPoint curP = [touch locationInView:self];

獲取當前手指的上一個點

CGPoint preP = [touch previousLocationInView:self];

計算X軸的偏移量

CGFloat offsetX = curP.x - preP.x;

計算Y軸的偏移量

CGFloat offsetY = curP.y - preP.y;

修改按鈕的形變,讓按鈕能夠移動.

self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);

}

第二步,實現hitTest方法,判斷手指當前所在的點在不在按鈕的子控件上.

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

把當前所在的點轉換成按鈕子控件上面的點

CGPoint chatP =? [self convertPoint:point toView:self.chatBtn];

判斷轉換后的點在不在按鈕的控件上.

if ([self.chatBtn pointInside:chatP withEvent:event]) {如果在

直接返回,也就意味著,當前最適合的View,就是這個按鈕

return self.chatBtn;

}else{如果不在,那么就保持系統原有做法.

return? [super hitTest:point withEvent:event];

}

}



相關知識擴充



關于手勢

值得注意的是:手勢識別器 是 先于touch方法(touch響應鏈!)?? 捕捉到touch object!!!

cancelsTouchesInView/delaysTouchesBegan/delaysTouchesEnded

(0)首先要知道的是

1.這3個屬性是作用于GestureRecognizers(手勢識別)與觸摸事件之間聯系的屬性。實際應用中好像很少會把它們放到一起,大多都只是運用手勢識別,所以這3個屬性應該很少會用到。

2.對于觸摸事件,window只會有一個控件來接收touch。這個控件是首先接觸到touch的并且重寫了觸摸事件方法(一個即可)的控件

3.手勢識別和觸摸事件是兩個獨立的事,只是可以通過這3個屬性互相影響,不要混淆。

4手勢是view外部來添加? ,? touch是view內部處理,兩個是分開,且手勢優先級比touch高

(1)在默認情況下(即這3個屬性都處于默認值的情況下)(這些屬性是 手勢對它自己的view!!!)

如果觸摸window,首先由window上最先符合條件的控件(該控件記為hit-test

view)接收到該touch并觸發觸摸事件touchesBegan。同時如果某個控件的手勢識別器接收到了該touch,就會進行識別。手勢識別成功之后發送觸摸事件touchesCancelled給hit-testview,hit-test

view不再響應touch。(即打斷 往上傳遞的響應鏈條)

(2)cancelsTouchesInView:(默認yes)

默認為YES,這種情況下當手勢識別器識別到touch之后,會發送touchesCancelled給hit-testview以取消hit-test view對touch的響應,這個時候只有手勢識別器響應touch。

當設置成NO時,手勢識別器識別到touch之后不會發送touchesCancelled給hit-test,這個時候手勢識別器和hit-test view均響應touch。

(3)delaysTouchesBegan:(默認no)

默認是NO,這種情況下當發生一個touch時,手勢識別器先捕捉到到touch,然后發給hit-testview,兩者各自做出響應。

如果設置為YES,手勢識別器在識別的過程中(注意是識別過程),不會將touch發給hit-test

view,即hit-testview不會有任何觸摸事件。!!只有在識別失敗之后才會將touch發給hit-testview,這種情況下hit-test

view的響應會延遲約0.15ms。

(4)delaysTouchesEnded:(默認yes)

默認為YES。這種情況下發生一個touch時,在手勢識別成功后,發送給touchesCancelled消息給hit-testview,手勢識別失敗時,會延遲大概0.15ms,期間沒有接收到別的touch才會發送touchesEnded。如果設置為NO,則不會延遲,即會立即發送touchesEnded以結束當前觸摸。

(5)手勢共存 與 排斥(以下是手勢對手勢!!)

1:[tapGesture? requireGestureRecognizerToFail:swipeGesture]

swipe判斷失敗后? 才判斷tap

2:(代理方法)-

(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer

*)otherGestureRecognizer

這里返回YES,代表跟別的手勢共存;如果返回NO,不一定代表不共存(可能另一個手勢返回yes就可以共存,只要兩個手勢任一返回yes就可以)

3:-

(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer

*)otherGestureRecognizer

另外一個手勢識別fail的時候,才會識別自己

4-

(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer

*)otherGestureRecognizer

我被另外一個手勢變成Fail

(6)button 是用這個方法發送時間(補充)

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event






關于scrollView

有很多新聞類的App頂部都有一個滑動菜單欄,主要模型可能是由一個UIScrollView包含多個UIButton控件組成;當你操作的時候,手指如果是很迅速的在上面劃過,會發現即使手指觸摸的地方有UIButton,但是并沒有觸發該UIButton的任何觸摸事件,這就是上面提到的case1;當你手指是緩慢劃過或根本就沒動,才會觸發UIButton的觸摸事件,這是case2的情況。

(0):scrollView默認封裝了兩個手勢 pan和pinch

(1):scrollView的touch事件傳遞的兩個重要的屬性delaysContentTouches和canCancelContentTouches

delaysContentTouches(默認yes)(這個屬性也是上面說scrollView默認阻斷 響應鏈的原因)

delay延遲對subView的touch響應,肯定會優先響應UIScrollview滑動事件

delaysContentTouches。默認值為YES;如果設置為NO,則無論手指移動的多么快,始終都會將觸摸事件傳遞給內部控件;設置為NO可能會影響到UIScrollView的滾動功能。


canCancelContentTouches(默認yes)

注意:如果scrollView(內部)- (BOOL)touchesShouldCancelInContentView:(UIView *)view
也設置,那兩個要配合,平時就不要寫這個方法

如果屬性值為YES并且跟蹤到手指正觸摸到一個內容控件,這時如果用戶拖動手指的距離足夠產生滾動,那么內容控件將收到一個touchesCancelled:withEvent:消息,而scroll

view將這次觸摸作為滾動來處理。如果值為NO,一旦content

view開始跟蹤(tracking==YES),則無論手指是否移動,scrollView都不會滾動。

簡單通俗點說,如果為YES,就會等待用戶下一步動作,如果用戶移動手指到一定距離,就會把這個操作作為滾動來處理并開始滾動,同時發送一個touchesCancelled:withEvent:消息給內容控件,由控件自行處理。如果為NO,就不會等待用戶下一步動作,并始終不會觸發scrollView的滾動了。




關于control

簡單區分UIResponder與UIControl

UIResponder類:上承NSObject,下接UIView ,UIVIewController ,UIApplacation;響應點,壓,滑;

UIControl類:上承UIView,下接UIButton等開關按鈕;


主要區別在于:

前者,主要是響應某個動作,執行某個行為--

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

后者,在繼承了前者的屬性基礎上,還能夠相應某個動作,為某個對象,添加動作--

- (void) addTarget:(id)target action:(SEL)action forControlEvents(UIControlEvents)controlEvents

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event

返回值:YES 接受用戶通過addTarget:action:forControlEvents添加的事件繼續處理。

返回值:NO??則屏蔽用戶添加的任何事件

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event可以用這個追蹤 button上touch的改變? (有點類似于viewControl里面的touchMoved)

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 如果用戶重寫了該方法,則不會執行由用戶添加的其他事件,直接屏蔽了用戶的事件



分派事件

使用下面兩個方法分派事件給響應者處理:

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event;

-

(void)sendActionsForControlEvents:(UIControlEvents)controlEvents;

// send all actions associated with events

事件與操作的區別:事件報告對屏幕的觸摸;操作報告對控件的操縱。



手勢共存 互斥案例




最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容