1.寫作緣起
在觸摸事件傳遞機制這個的問題上連自己都覺著不就是老掉牙的Hit-Testingt么,遞歸遍歷,找到最合適的view,然后把事件傳遞給它,如果它處理不了那就往它的下一個響應(yīng)者傳遞,如果一直不能處理這個事件就將其丟棄.
不論是自己學習還是說給面試官都是認為就是這么回事,而且蘋果的官方文檔(點此處)也確實有這樣的論述.
The hit-test view is given the first opportunity to handle a touch event. If the hit-test view cannot handle an event, the event travels up that view’s chain of responders as described in The Responder Chain Is Made Up of Responder Objects until the system finds an object that can handle it.
文檔反復讀了幾遍,Hit-Testing Returns the View Where a Touch Occurred,這句話還有我們平時在開發(fā)中應(yīng)用不斷積累下來的理解我們很容易就總結(jié)出Hit-Testing就是找到了touch發(fā)生的那個view.然后就向上面的引用說的那樣了,但是當晚上跟室友復現(xiàn)這個問題的時候,本來想著簡直無懈可擊啊,文檔又不是第一次看,又不是沒使用過這個原理解決問題.可是問題來了,自己問了自己一個問題:既然這個view可以處理這個事件,那么這個事件究竟是如何被處理的?換句話說手勢是如何被識別出來的呢?僅僅是等待hit-test view判斷不能處理之后再交給父view去處理么?假如是這樣,那么如果羅列了100個view,每個view的手勢不一樣,有的是連續(xù)手勢,比如縮放,那么以極限思維去思考,這個處理的時間是不是會像蝸牛一樣呢?結(jié)論只有一個==我不知道也說不清楚這個具體的處理過程,想想自己對于這個問題之前的學習思考也太想當然了,群體都是盲從的,大部分的帖子也都是按照官方文檔這個籠統(tǒng)的意思去解釋的,當把這些問題拋出來給自己的時候,也就是這篇文章的緣起(對佛學有了解的同學們對緣起應(yīng)該有更深刻的理解,順便說一句,在下學禪多年,有同道中人可以一起學習).
2.解決疑惑--Google
資料不少,但是最為系統(tǒng)的論述還是蘋果的文檔(點此出),下面就直接說我理解出來的和我已經(jīng)驗證了的結(jié)果吧,當然還是建議大家把文檔仔細讀一讀,寫demo推敲,在下要是說錯了感謝給予指正,先構(gòu)建這樣一個view的層級關(guān)系

這里的view以及所加手勢都繼承寫出自己的子類,這樣我們就可以重寫父類的方法了.代碼如下
- (void)viewDidLoad {
[super viewDidLoad];
[self createViewAndGes];
}
- (void)createViewAndGes
{
//1.容器view
WYContainerView *viewContainer = [[WYContainerView alloc]initWithFrame:self.view.bounds];
[self.view addSubview:viewContainer];
//--屬性設(shè)置
viewContainer.backgroundColor = [UIColor purpleColor];
//--添加pinch手勢
WYPinchGesture * pinchges = [[WYPinchGesture alloc]initWithTarget:self action:@selector(pinchAction)];
pinchges.delegate = pinchges;
[viewContainer addGestureRecognizer:pinchges];
//2.上部分的view
WYViewUp * viewUp = [[WYViewUp alloc]initWithFrame:CGRectMake(0, 40, self.view.frame.size.width, 150)];
[viewContainer addSubview:viewUp];
//--屬性設(shè)置
viewUp.backgroundColor = [UIColor redColor];
//--添加自定義的tap手勢
WYTapGesUp * tapUp = [[WYTapGesUp alloc]initWithTarget:self action:@selector(tapUpAction)];
tapUp.delegate = tapUp;
[viewUp addGestureRecognizer:tapUp];
//3.下部分view
WYViewDown *viewDown = [[WYViewDown alloc]initWithFrame:CGRectMake(0, 300, self.view.frame.size.width, 150)];
[viewContainer addSubview:viewDown];
//--屬性設(shè)置
viewDown.backgroundColor = [UIColor blueColor];
//--添加自定義的手勢
WYTapGesDown *tapDown = [[WYTapGesDown alloc]initWithTarget:self action:@selector(tapDownAction)];
tapDown.delegate = tapDown;
[viewDown addGestureRecognizer:tapDown];
}
- (void)pinchAction
{
NSLog(@"%s",__func__);
}
- (void)tapUpAction
{
NSLog(@"%s",__func__);
}
- (void)tapDownAction
{
NSLog(@"%s",__func__);
}
首先在每個view中重寫下面的方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
return [super hitTest:point withEvent:event];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
NSLog(@"%s",__func__);
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
NSLog(@"%s",__func__);
[super touchesEnded:touches withEvent:event];
}
再次讓每個手勢子類都實現(xiàn)下面的方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
NSLog(@"%s",__func__);
return YES;
}
結(jié)論如下:
當我們點擊viewUp時,并不是等待viewUp完全判斷自己不能處理這個事件之后再向下傳遞事件,而實際情況是這樣的,手勢識別器會先于綁定的view拿到這些touch,手勢識別器中同樣有touchesBegan:withEvent: 等touch方法,手勢識別器是一個有限狀態(tài)機,當hit-testing完畢,touch發(fā)生的view拿到之后(hit-test是一個遞歸),這條響應(yīng)者鏈也就被app拿到了,此時touch開始向這條響應(yīng)者鏈上的所有手勢識別器分發(fā),分發(fā)當然也得有個次序了,此時還是hit-test的手勢識別器先拿到touch,然后狀態(tài)機啟動.哪個識別出:哦,這個無論是點擊類型還是綁定的view跟我匹配,我觸發(fā)action去了.
注意:此時是這鏈條上的所有手勢識別器都會先于所綁定的view按一定次序開始觸發(fā)狀態(tài)機,不是依次等待上一個識別器有結(jié)果之后出發(fā)下一個,而且即使我們屏蔽了自定義view中touches方法,就是不調(diào)用super,那么手勢識別器一樣會觸發(fā)action,也就是說view里面的touches方法并不影響手勢的識別和事件的分發(fā),屏蔽這個測試大家可以自己試一下
我們tap viewUp看一下控制臺的打印,結(jié)果我們可以看出手勢對象會先于所綁定的view拿到touch,并且絕不是viewUp的手勢完全處理完畢后再讓其父控件處理.
2016-11-13 13:45:57.690 ****混合手勢****[22803:5468856] -[WYContainerView hitTest:withEvent:]
2016-11-13 13:45:57.690 ****混合手勢****[22803:5468856] -[WYViewDown hitTest:withEvent:]
2016-11-13 13:45:57.691 ****混合手勢****[22803:5468856] -[WYViewUp hitTest:withEvent:]
2016-11-13 13:45:57.691 ****混合手勢****[22803:5468856] -[WYContainerView hitTest:withEvent:]
2016-11-13 13:45:57.691 ****混合手勢****[22803:5468856] -[WYViewDown hitTest:withEvent:]
2016-11-13 13:45:57.692 ****混合手勢****[22803:5468856] -[WYViewUp hitTest:withEvent:]
2016-11-13 13:45:57.692 ****混合手勢****[22803:5468856] -[WYTapGesUp gestureRecognizer:shouldReceiveTouch:]
2016-11-13 13:45:57.693 ****混合手勢****[22803:5468856] -[WYPinchGesture gestureRecognizer:shouldReceiveTouch:]
2016-11-13 13:45:57.694 ****混合手勢****[22803:5468856] -[WYViewUp touchesBegan:withEvent:]
2016-11-13 13:45:57.695 ****混合手勢****[22803:5468856] -[WYContainerView touchesBegan:withEvent:]
2016-11-13 13:45:57.820 ****混合手勢****[22803:5468856] -[ViewController tapUpAction]
如果說那個代理方法不能讓我們十分確認,那么我就就在viewUp的手勢類中重寫
touchesBegan:withEvent:方法,但是我們并不能調(diào)用super了,雖然此時不能響應(yīng)action了,但是我們可以看到確實是手勢識別器先拿到touches
打印結(jié)果如下:看第三行
2016-11-13 14:00:52.476555 ****混合手勢****[5217:1428180] -[WYTapGesUp gestureRecognizer:shouldReceiveTouch:]
2016-11-13 14:00:52.476776 ****混合手勢****[5217:1428180] -[WYPinchGesture gestureRecognizer:shouldReceiveTouch:]
2016-11-13 14:00:52.478008 ****混合手勢****[5217:1428180] -[WYTapGesUp touchesBegan:withEvent:]
2016-11-13 14:00:52.479009 ****混合手勢****[5217:1428180] -[WYViewUp touchesBegan:withEvent:]
2016-11-13 14:00:52.479221 ****混合手勢****[5217:1428180] -[WYContainerView touchesBegan:withEvent:]
2016-11-13 14:00:52.522895 ****混合手勢****[5217:1428180] -[WYViewUp touchesEnded:withEvent:]
2016-11-13 14:00:52.523205 ****混合手勢****[5217:1428180] -[WYContainerView touchesEnded:withEvent:]
那么咱們看看官方文檔是如何總結(jié)的
In the simple case, when a touch occurs, the touch object is passed from the UIApplication object to the UIWindow object. Then, the window first sends touches to any gesture recognizers attached the view where the touches occurred (or to that view’s superviews), before it passes the touch to the view object itself.
Gesture Recognizers Get the First Opportunity to Recognize a Touch
A window delays the delivery of touch objects to the view so that the gesture recognizer can analyze the touch first. During the delay, if the gesture recognizer recognizes a touch gesture, then the window never delivers the touch object to the view, and also cancels any touch objects it previously sent to the view that were part of that recognized sequence.

以上這些內(nèi)容可以解釋手勢識別器的優(yōu)先級是比所綁定的視圖高的,而且也不是所有的touch都會傳遞到view,
到這里我們不妨再推敲一下,為什么要這樣設(shè)計這個機制呢?不能等viewup判斷自己能否處理之后再往下傳遞么?
答:如果是父view是縮放手勢,如果按照依次傳遞會怎么樣?可以看出在處理的時效性和準確性方面不如這么設(shè)計好.
那蘋果的文檔在hit-test說的就是最上面的view處理不了再交給后面的view啊?這不矛盾么?
答:這不矛盾,我們看文檔不能斷章取義,不能太機械,蘋果在hit-test中說的是一種宏觀上的表現(xiàn)形式.
hit-test的目標就是抓住touch對應(yīng)的響應(yīng)者鏈的頭,這樣我們就可以分發(fā)了,不然我們?nèi)绾胃咝シ职l(fā)呢?
最后我們探索一下這個響應(yīng)者鏈的頭是如何的關(guān)鍵
我們打破一下這個鏈條,看看這個頭一旦拿錯了后果是多么的有意思
#import "WYViewUp.h"
@implementation WYViewUp
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
return [UIView new];
return [super hitTest:point withEvent:event];
}
點擊viewUp,看打印結(jié)果:
2016-11-13 14:31:19.914369 ****混合手勢****[5232:1432814] -[WYContainerView hitTest:withEvent:]
2016-11-13 14:31:19.914694 ****混合手勢****[5232:1432814] -[WYViewDown hitTest:withEvent:]
2016-11-13 14:31:19.914917 ****混合手勢****[5232:1432814] -[WYViewUp hitTest:withEvent:]
2016-11-13 14:31:19.915918 ****混合手勢****[5232:1432814] -[WYContainerView hitTest:withEvent:]
2016-11-13 14:31:19.916102 ****混合手勢****[5232:1432814] -[WYViewDown hitTest:withEvent:]
2016-11-13 14:31:19.916263 ****混合手勢****[5232:1432814] -[WYViewUp hitTest:withEvent:]
這里我們看到touch根本無法分發(fā).
大膽猜想
蘋果沒有吧touches方法在手勢識別器中暴露給我們估計是不方便我們使用,因為我們面對view更直接一些,不然我們需要用這些數(shù)據(jù)的時候還要剝離手勢識別器,目前來看我們直接重寫view就行了.