iOS事件的响应和传递机制
跟二狗子哥哥交流的時候,他總說我,說的過程太業余。故 好好學習整理一下。努力不那么業余。
一、事件的產生、傳遞、響應:
1、事件從父控件依次傳遞到子控件,尋找最合適的子控件View。
2、尋找最合適的View的底層實現,攔截事件的處理。
3、找到最合適的view之后的事件處理,也就是事件響應,重寫touch方法等。
?
傳遞過程中比較重要的兩點:
1.如何尋找最合適的view
2.尋找最合適的view的底層實現(hitTest:withEvent:底層實現)
觸摸事件 加速事件 遠程控制事件 我們現在 討論觸摸事件
?
二、響應者對象(UIResponder)
只有繼承了UIResponder的對象,才可以接受并處理事件,我們稱之為響應者對象
響應者對象有:UIApplication UIViewController UIView
UIResponder提供了方法以下方法處理觸摸事件:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
?
Presses事件
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
?
UIResponder提供了方法以下方法處理加速事件:
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
?
UIResponder提供了方法以下方法處理遠程事件:
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);
?
另外還有:
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(3_0);
?
允許一個事件處理是可轉發給其他對象, 默認情況是 調用方法?-canPerformAction:withSender: 來返回自己或者提交返回給響應鏈去判斷處理。
// Allows an action to be forwarded to another target. By default checks -canPerformAction:withSender: to either return self, or go up the responder chain.
- (nullable id)targetForAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(7_0)
?
三、事件傳遞:用戶的觸摸事件,首先被封裝成一個UIEvent事件,然后UIEvent事件傳遞給UIResponder的事件,進行判斷,尋找最合適的View。
UIEvent事件中會有UITouch對象集,保存著UITouch對象
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
一根手指對應一個UITouch對象,
如果兩根手指同事觸摸,會調用一次- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;,其中的UIEvent事件中有兩個UITouch對象
如果兩根手指先后觸摸,會調用兩次- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;方法,每個方法的UIEvent事件中各有一個UITouch對象
UITouch中保存著手指相關的信息,觸摸的位置,時間,階段。
當手指移動時,系統會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置
當手指離開屏幕時,系統會銷毀相應的UITouch對象
事件產生之后,系統會將事件添加到UIApplication管理的事件隊列中,UIApplication會從事件隊列中取出最前面的事件,并將事件分發下去處理,先將事件發送給應用程序的主窗口 keyWindow。
keyWindow會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個事件處理過程的第一步。
事件產生之后,就是事件的傳遞過程:
從父控件到子控件尋找合適的視圖控件:
? ? ? ? ?- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
? ? ? ? ? recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
? ? ? ? ?遞歸調用-pointInside:withEvent:方法,判斷point是否在其范圍內
?
- 1.首先判斷主窗口(keyWindow)自己是否能接受觸摸事件
?
?
- 2.判斷觸摸點是否在自己身上(pointInside方法)
? ? ? ?- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
? ? ? ? ?// default returns YES if point is in bounds ?如果點在bouns范圍內,默認返回yes
- 3.子控件數組中從后往前遍歷子控件,重復前面的兩個步驟(所謂從后往前遍歷子控件,就是首先查找子控件數組中最后一個元素,然后執行1、2步驟)
- 4.view,比如叫做fitView,那么會把這個事件交給這個fitView,再遍歷這個fitView的子控件,直至沒有更合適的view為止。
- 5.如果沒有符合條件的子控件,那么就認為自己最合適處理這個事件,也就是自己是最合適的view。
期間用到的重要的兩個方法:
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
?- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event
UIView不能接收觸摸事件的三種情況:
- 不允許交互:userInteractionEnabled = NO
- 隱藏:如果把父控件隱藏,那么子控件也會隱藏,隱藏的控件不能接受事件
- 透明度:如果設置一個控件的透明度<0.01,會直接影響子控件的透明度。alpha:0.0~0.01為透明。
主要方法解析:
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
調用時機:
只要事件一傳給某個控件,控件就會調用自己的該方法,尋找合適的子控件返回。
作用:
尋找合適的子控件返回
注 意:不管這個控件能不能處理事件,也不管觸摸點在不在這個控件上,事件都會先傳遞給這個控件,隨后再調用hitTest:withEvent:方法
?- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event
攔截事件的處理:
- 正因為hitTest:withEvent:方法可以返回最合適的view,所以可以通過重寫hitTest:withEvent:方法,返回指定的view作為最合適的view。
- 不管點擊哪里,最合適的view都是hitTest:withEvent:方法中返回的那個view。
- 通過重寫hitTest:withEvent:,就可以攔截事件的傳遞過程,想讓誰處理事件誰就處理事件。
如果hitTest:withEvent:方法中返回nil,那么調用該方法的控件本身和其子控件都不是最合適的view,也就是在自己身上沒有找到更合適的view。那么最合適的view就是該控件的父控件。
事件傳遞順序:
產生觸摸事件->UIApplication事件隊列->[UIWindow hitTest:withEvent:]->返回更合適的view->[子控件 hitTest:withEvent:]->返回最合適的view
重寫的技巧:在父控件的hitTest:withEvent:中返回子控件作為最合適的view!
?
hit:withEvent:方法底層會調用pointInside:withEvent:方法判斷點在不在方法調用者的坐標系上
?
?
pointInside:withEvent:方法
判斷點在不在當前view上(方法調用者的坐標系上)如果返回YES,代表點在方法調用者的坐標系上;返回NO代表點不在方法調用者的坐標系上,那么方法調用者也就不能處理事件。
?
四、事件的響應:
響應者鏈條
如何判斷上一個響應者
- 1> 如果當前這個view是控制器的view,那么控制器就是上一個響應者
- 2> 如果當前這個view不是控制器的view,那么父控件就是上一個響應者
響應者鏈的事件傳遞過程:
- 1>如果當前view是控制器的view,那么控制器就是上一個響應者,事件就傳遞給控制器;如果當前view不是控制器的view,那么父視圖就是當前view的上一個響應者,事件就傳遞給它的父視圖
- 2>在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理
- 3>如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
- 4>如果UIApplication也不能處理該事件或消息,則將其丟棄
五、事件處理的整個流程總結:
1.觸摸屏幕產生觸摸事件后,觸摸事件會被添加到由UIApplication管理的事件隊列中(即,首先接收到事件的是UIApplication)。
2.UIApplication會從事件隊列中取出最前面的事件,把事件傳遞給應用程序的主窗口(keyWindow)。
3.主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件。(至此,第一步已完成)
4.最合適的view會調用自己的touches方法處理事件
5.touches默認做法是把事件順著響應者鏈條向上拋。
參考:http://www.cnblogs.com/machao/p/5471094.html
十分感謝前輩的分享
轉載于:https://www.cnblogs.com/Jordandan/p/6483835.html
總結
以上是生活随笔為你收集整理的iOS事件的响应和传递机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 预编译对象解决SQL注入问题
- 下一篇: codevs 1283 等差子序列