iOS最常見的是觸摸事件Touch Events燃异。觸摸事件除了是view來處理吴裤,還有高級的手勢可以處理缸兔。所以日裙,本文分別來講講觸摸事件和手勢,并結合例子講講兩者混合使用的問題惰蜜。
UITouch對象
一個手指第一次點擊屏幕昂拂,就會生成一個UITouch對象,到手指離開時銷毀抛猖。當我們有多個手指觸摸屏幕時,會生成多個UITouch對象财著。UITouch對象可以表明觸摸的位置联四、狀態(tài)。
UIEvent對象
一個UIEvent對象代表iOS的一個事件撑教。一個觸摸事件定義為第一個手指開始觸摸屏幕到最后一個手指離開屏幕朝墩。所以,一個UIEvent對象實際上對應多個UITouch對象伟姐。
響應鏈
響應鏈可以理解為一種虛擬的鏈表收苏,每一個節(jié)點是一個UIResponder對象。UIResponder是事件接收與處理的基類愤兵,UIApplication鹿霸、UIViewController和UIView都繼承自UIResponder。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;
UIResponder對象之間的聯(lián)系靠nextResponder指針秆乳。
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
#else
- (nullable UIResponder*)nextResponder;
#endif
一個觸摸事件到達真正處理它的對象時經(jīng)過了一個搜索路徑懦鼠,這就是響應鏈的一部分。事件沿著這個響應鏈一直傳遞,直到碰到可以處理這個事件的UIResponder對象或者到達響應鏈的末尾(UIApplication)葛闷。
響應鏈的構造規(guī)則如下:
- 程序啟動時憋槐,UIApplication會生成一個單例,并會關聯(lián)一個APPDelegate淑趾。APPDelegate作為整個響應鏈的根建立起來阳仔,UIApplication的nextResponser為APPDelegate。
- 程序啟動后扣泊,任何的UIWindow被創(chuàng)建時近范,UIWindow內(nèi)部都會把nextResponser設置為UIApplication單例。
- UIWindow初始化rootViewController, rootViewController的nextResponser會設置為UIWindow延蟹。
- UIViewController初始化View评矩,View的nextResponser會設置為rootViewController。
-
AddSubView時阱飘,如果subView不是ViewController的View,那么subView的nextResponser會被設置為superView斥杜。否則就是 subView -> subView.VC ->superView。
有了這個響應鏈沥匈,事件就可以按照這個路徑逐級傳遞了蔗喂。當前的對象不能處理這個事件,就交給nextResponser高帖,一直到UIApplication單例缰儿。如果仍然不能處理,就丟棄散址。
Hit-Testing
當我們觸摸屏幕時乖阵,到底應該由哪個對象最先響應這個事件呢?這就需要去探測预麸,這個過程稱為Hit-Testing瞪浸,最后的結果稱為hit-test view。涉及到兩個方法是:
//先判斷點是否在View內(nèi)部吏祸,然后遍歷subViews
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判斷點是否在這個View內(nèi)部
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Hit-Testing是一個遞歸的過程默终,每一步監(jiān)測觸摸位置是否在當前view中,如果是犁罩,就遞歸監(jiān)測subviews齐蔽;否則,返回nil床估。
遞歸的根節(jié)點是UIWindow含滴,對subviews的遍歷順序按照 后添加的先遍歷 原則。
手勢識別
手勢是Apple提供的更高級的事件處理技術丐巫,可以完成更多更復雜的觸摸事件谈况,比如旋轉勺美、滑動、長按等碑韵。
手勢綁定到一個View上赡茸,一個View上可以綁定多個手勢。
UIGestureRecognizer同UIResponder一樣也有四個方法:
//UIGestureRecognizer
- (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;
手勢會在以上四個方法中去對手勢的State做更改祝闻,手勢的State表明當前手勢是識別還是失敗等等占卧。比如單擊手勢會在touchesBegan 時記錄點擊位置,然后在touchesEnded判斷點擊次數(shù)联喘、時間华蜒、是否移動過,最后得出否識別該手勢豁遭。這幾個方法一般在自定義手勢里面使用叭喜。
手勢識別與事件響應混用
1 觸摸事件首先傳遞到手勢上,如果手勢識別成功蓖谢,就會取消事件的繼續(xù)傳遞捂蕴,否則,事件會被響應鏈處理闪幽。具體地啥辨,系統(tǒng)維持了與響應鏈關聯(lián)的所有手勢,事件首先發(fā)給這些手勢沟使,然后再發(fā)給響應鏈委可。
2 在iOS 6.0 或以后版本中渊跋,默認控件操作方法會阻斷重復手勢的識別行為腊嗡。
一個按鈕的默認操作是一個單擊。如果你有一個單擊手勢識別綁定到一個按鈕的父視圖上拾酝,然后用戶點擊該按鈕燕少,最后按鈕的操作方法接收觸摸事件而不是手勢識別。 它只用于手勢識別跟一個控件的默認操作重復時蒿囤。