1寿桨、響應(yīng)鏈的傳遞
Responder一點(diǎn)也不神秘————iOS用戶響應(yīng)者鏈完全剖析(建議全看)
看完上面一篇應(yīng)該能完全熟悉了響應(yīng)鏈的傳遞此衅,自己可以打印一下響應(yīng)鏈看看,代碼如下:
- (IBAction)click:(id)sender {
UIResponder *res = sender;
while (res) {
NSLog(@"*************************************\n%@",res);
res = [res nextResponder];
}
}
2、Hit-Test 機(jī)制
當(dāng)用戶觸摸(Touch)屏幕進(jìn)行交互時(shí)炕柔,系統(tǒng)首先要找到響應(yīng)者(Responder)酌泰。系統(tǒng)檢測到手指觸摸(Touch)操作時(shí),將Touch 以UIEvent的方式加入U(xiǎn)IApplication事件隊(duì)列中匕累。UIApplication從事件隊(duì)列中取出最新的觸摸事件進(jìn)行分發(fā)傳遞到UIWindow進(jìn)行處理陵刹。UIWindow 會(huì)通過hitTest:withEvent:方法尋找觸碰點(diǎn)所在的視圖,這個(gè)過程稱之為hit-test view欢嘿。
hitTest 的順序如下
UIApplication -> UIWindow -> Root View -> ··· -> subview
在頂級(jí)視圖(Root View)上調(diào)用pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi)衰琐;
如果返回NO,那么hitTest:withEvent:返回nil炼蹦;
如果返回YES羡宙,那么它會(huì)向當(dāng)前視圖的所有子視圖發(fā)送hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖掐隐,即從subviews數(shù)組的末尾向前遍歷狗热,直到有子視圖返回非空對(duì)象或者全部子視圖遍歷完畢。
如果有subview的hitTest:withEvent:返回非空對(duì)象則A返回此對(duì)象虑省,處理結(jié)束(注意這個(gè)過程匿刮,子視圖也是根據(jù)pointInside:withEvent:的返回值來確定是返回空還是當(dāng)前子視圖對(duì)象的。并且這個(gè)過程中如果子視圖的hidden=YES探颈、userInteractionEnabled=NO或者alpha小于0.1都會(huì)并忽略)熟丸;
如果所有subview遍歷結(jié)束仍然沒有返回非空對(duì)象,則hitTest:withEvent:返回self伪节;
系統(tǒng)就是這樣通過hit test找到觸碰到的視圖(Initial View)進(jìn)行響應(yīng)光羞。
如果還不清楚Hit-Test 機(jī)制,看更加清晰的Hit-Test 機(jī)制(建議還不清楚的看)
3怀大、手勢的原理及與touches系列的關(guān)系纱兑,具體的可以看iOS觸摸事件傳遞響應(yīng)之被忽視的手勢識(shí)別器工作原理(建議不看也沒關(guān)系,結(jié)論在下面了化借。)
簡而言之萍启,就是下面這幅圖了。觸摸事件會(huì)優(yōu)先分發(fā)給附在view的手勢屏鳍,在這段延遲的期間,如果手勢被識(shí)別局服,那么view的touches系列將被立刻取消钓瞭,如果沒有被識(shí)別,那么會(huì)繼續(xù)我們所熟知的touches系列流程淫奔。
4山涡、實(shí)際開發(fā)中常見的相關(guān)問題
在實(shí)際開發(fā)中,經(jīng)常會(huì)遇到視圖沒有響應(yīng)的情況,特別是新手會(huì)經(jīng)常搞不清楚狀況鸭丛。
一下是視圖沒有響應(yīng)的幾個(gè)情況:
1.userInteractionEnabled=NO竞穷;
2.hidden=YES;
3.alpha=0~0.01鳞溉;
4.沒有實(shí)現(xiàn)touchesBegan:withEvent:方法瘾带,直接執(zhí)行touchesMove:withEvent:等方法;
5.目標(biāo)視圖點(diǎn)擊區(qū)域不在父視圖的Frame上 (superView背景色為clear Color的時(shí)候經(jīng)常會(huì)忽略這個(gè)問題)熟菲。
5看政、手勢代理
ios手勢識(shí)別代理,看這個(gè)基本上就夠了抄罕。引用文章中的一段話允蚣,如下:
- 當(dāng)時(shí)做項(xiàng)目時(shí)這個(gè)主控制器就是RootViewController,雖然用的是ScrollView但也沒考慮到導(dǎo)航欄的手勢返回的問題 ,現(xiàn)在做小區(qū)寶3.0的閃購訂單呆贿,用之前的就有問題了嚷兔。導(dǎo)航欄的返回手勢用不了,根據(jù)響應(yīng)者鏈和響應(yīng)事件,手勢被ScrollView識(shí)別了做入,就到不了導(dǎo)航的手勢識(shí)別冒晰,所以導(dǎo)致無法手勢返回。
我也曾經(jīng)處理過這樣的問題母蛛,不過我那時(shí)候是帶有QQ的側(cè)滑功能翩剪,主控制器用的View是ScrollView,導(dǎo)致不能側(cè)滑彩郊。但是處理的方法都是一樣的前弯,自定義的ScrollView的代碼重寫gestureRecognizerShouldBegin方法如下,我是手勢方向向右并且x軸起點(diǎn)小于60px的秫逝,讓ScrollView的手勢失效恕出。這樣就不會(huì)截獲對(duì)應(yīng)的事件了。但是其實(shí)看完上面违帆,還有更簡單的方法浙巫,就是讓ScrollView的手勢共存,但是這樣可能會(huì)帶來一些其它的問題刷后。shouldRecognizeSimultaneouslyWithGestureRecognizer設(shè)置為true的畴,不過應(yīng)該要判斷手勢為UIScreenEdgePanGestureRecognizer時(shí)才return true,這樣就可以了尝胆。
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
CGPoint location = [gestureRecognizer locationInView:self];
NSLog(@"velocity.x:%f----location.x:%d",velocity.x,(int)location.x%(int)[UIScreen mainScreen].bounds.size.width);
if (velocity.x > 0.0f&&(int)location.x%(int)[UIScreen mainScreen].bounds.size.width<60) {
return NO;
}
return YES;
}
案例分析
案例一
下面這種做法丧裁,除非你很熟悉,否則不要這么干含衔。因?yàn)?[super touchesBegan:touches withEvent:event];會(huì)執(zhí)行原來默認(rèn)的操作煎娇,如果按鈕本來就沒有添加對(duì)應(yīng)的事件二庵。那么[[self nextResponder] touchesBegan:touches withEvent:event];和[super touchesBegan:touches withEvent:event];將會(huì)向下一響應(yīng)者發(fā)送兩次事件。
-(void)touchesBegan:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event{
if (self.enableNextResponder) {
[[self nextResponder] touchesBegan:touches withEvent:event];
[super touchesBegan:touches withEvent:event];
}
}
案例二
Window
-ViewA(能響應(yīng))
-ButtonA
-ViewB(不響應(yīng))
假設(shè)ViewB完全覆蓋在ButtonA上缓呛,結(jié)果是:
ViewA能觸發(fā)
Button沒反應(yīng)
ViewB沒反應(yīng)
簡單來說催享,ViewB能阻隔ButtonA的響應(yīng),但是不能阻隔ViewA的響應(yīng)哟绊。假設(shè)ViewB是個(gè)遮罩因妙,那么并不能阻隔ViewA的事件觸發(fā)。假設(shè)需要阻隔ViewA的相應(yīng)匿情,那么可以在ViewB添加空事件的相應(yīng)兰迫,從而達(dá)到ViewB(遮罩)阻隔ViewA(如游戲?qū)樱┑氖录鄳?yīng)。
案例三
一個(gè)按鈕添加了點(diǎn)擊事件到底發(fā)生了什么事兒炬称。
我們有時(shí)候需要使用到一些特殊的情況汁果,比如:
1、A包含B玲躯,AB都響應(yīng)事件据德。
對(duì)于普通View,根據(jù)響應(yīng)鏈跷车,讓B作為第一個(gè)響應(yīng)者處理棘利,然后B根據(jù)nextResponder傳遞觸摸事件。
針對(duì)手勢做分析:
手勢不會(huì)走view的touches系列方法朽缴,但有自己的一系列touches方法善玫,不過沒有暴露出來。但是shouldRecognizeSimultaneouslyWithGestureRecognizer也可以做到密强。
針對(duì)UIButton的分析:
UIButton addTarget分析茅郎,addTarget是UIControl的方法,其實(shí)addTarget的方法原理是或渤,UIControl對(duì)touches的觸摸事件的封裝系冗。[super touchesBegan:touches withEvent:event];包括了對(duì)
事件的封裝處理,如果重新了[super touchesBegan:touches withEvent:event];薪鹦,并且里面什么都不實(shí)現(xiàn)掌敬,那么當(dāng)前UIButton添加的addTarget所綁定的所有事件都不會(huì)觸發(fā)。因?yàn)楦采w了父類UIControl的封裝方法池磁。
如果我想一個(gè)按鈕的事件觸發(fā)奔害,并且它的下一響應(yīng)者也能觸發(fā)相應(yīng)的事件。那么該怎么處理呢地熄?
我們?cè)诎粹o上處理舀武,重寫touchesBegan系列的方法,那么根據(jù)上面所說离斩,必須要調(diào)用super的方法银舱,并且主動(dòng)像下一響應(yīng)者[self nextResponder]發(fā)送touchesBegan系列的方法。
2跛梗、A包含B寻馏、C,C在B的上面核偿,但是想讓B接收事件诚欠,C不接收事件
這種可以這么處理,自定義C的View漾岳,重寫hitTest:withEvent方法轰绵,返回nil,這樣自定義C的View及其子類都不會(huì)攔截事件尼荆。這樣B就可以順利處理事件左腔。
還可以把C的userInteractionEnabled設(shè)置為NO
3、A是B捅儒、C的父視圖液样,C在B的上面,這時(shí)候巧还,CB都處理事件鞭莽。這樣到底行不行?根據(jù)響應(yīng)鏈麸祷,這樣應(yīng)該是不靠譜的了澎怒。在C的touches方法中調(diào)用C的touches方法,然后重寫B(tài)的touches方法阶牍,但是這樣怪怪的喷面。有什么高招也請(qǐng)多多指教。貌似也沒有這樣的必要荸恕。
最后還發(fā)現(xiàn)了一篇一步到位的iOS響應(yīng)者鏈的全過程:iOS觸摸事件的流動(dòng)(想有更清晰的了解的看)
直接引用里面的一張圖: