一腹尖、UIView與CALayer區(qū)別
備注:這里的backing store指的是位圖。位圖最終是給計(jì)算機(jī)硬件操作的叙淌。
- CALayer為UIView提供顯示的內(nèi)容慢宗,只負(fù)責(zé)內(nèi)容顯示,不參與事件處理余素。
- UIView作為CALayer的代理豹休,提供交互操作;負(fù)責(zé)處理觸摸事件桨吊,參與響應(yīng)鏈威根。
二、為什么UIView只負(fù)責(zé)事件傳遞视乐、CALayer負(fù)責(zé)視圖顯示
這個(gè)問題等同于為什么iOS中提供UIView和CALayer兩個(gè)平行的層級結(jié)構(gòu)洛搀。
答:主要是為了做到單一職責(zé)原則,做到職責(zé)分離佑淀,避免過多重復(fù)代碼留美。
三、為什么CALayer不能響應(yīng)觸摸事件
從繼承關(guān)系圖來回答伸刃,響應(yīng)事件必須繼承自UIResponder谎砾。
四、事件傳遞
4.1奕枝、相關(guān)事件方法
//返回當(dāng)前響應(yīng)事件的視圖View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
//判斷當(dāng)前點(diǎn)擊的位置point是否在視圖范圍內(nèi)
//在hitTest: withEvent:內(nèi)部使用棺榔,用來判斷點(diǎn)擊了哪個(gè)View
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
4.2瓶堕、簡述事件傳遞流程
1隘道、點(diǎn)擊屏幕某一位置,這個(gè)事件會(huì)傳遞給UIApplication郎笆。
2谭梗、UIApplication傳遞給當(dāng)前的UIWindow。
3宛蚓、在UIWindow里面就會(huì)使用hitTest:withEvent:方法激捏,返回最終響應(yīng)的視圖。
4凄吏、在hitTest:withEvent:方法里远舅,會(huì)調(diào)用pointInside:withEvent:方法闰蛔,來判斷當(dāng)前點(diǎn)擊的位置是否在UIWindow內(nèi)。
5图柏、如果當(dāng)前的點(diǎn)在UIWindow內(nèi)序六,則遍歷UIWindow子視圖,查找最終響應(yīng)事件的視圖例诀;需要注意的是,這里的遍歷是倒序遍歷,即后添加的最優(yōu)先被點(diǎn)擊。
6坏瘩、在Subviews中的子視圖中,采用倒序遍歷views哪自。在每個(gè)view中都會(huì)調(diào)用hitTest:withEvent:,在view的子視圖中同樣會(huì)調(diào)用hitTest:withEvent:,也就是一直遞歸調(diào)用。
如果當(dāng)前view的hitTest:withEvent返回的不為nil悲没,則這個(gè)視圖就作為事件響應(yīng)的視圖,結(jié)束了事件傳遞的流程渗鬼;否則,繼續(xù)遍歷其它view。
如果整個(gè)Subviews都沒有找到,則當(dāng)前UIWindow就作為事件響應(yīng)的視圖。
備注:這里的事件響應(yīng)視圖崇猫,不如叫做命中視圖。因?yàn)辄c(diǎn)擊了這個(gè)視圖,但是這個(gè)視圖不一定能為當(dāng)前事件綁定了一個(gè)觸發(fā)函數(shù)议纯,也就是不能響應(yīng)了。
這個(gè)時(shí)候,就會(huì)沿著響應(yīng)鏈向上尋找包晰,看看父節(jié)點(diǎn)是否能夠響應(yīng)赫模,這就是下面的響應(yīng)鏈。
4.3蒸矛、hitTest:withEvent:內(nèi)部實(shí)現(xiàn)
1瀑罗、判斷hidden=YES、userInteractionEnabled=YES雏掠、alpha<0.01斩祭。
如果不滿足上述條件,則會(huì)返回nil乡话,父類繼續(xù)遍歷其它子視圖摧玫。
2、使用pointInside:withEvent:绑青,判斷點(diǎn)擊的point是否在視圖范圍內(nèi)诬像。
如果不滿足上述條件,則會(huì)返回nil闸婴,父類繼續(xù)遍歷其它子視圖坏挠。
3、上述條件滿足邪乍,則會(huì)采用倒序方法遍歷當(dāng)前子視圖
4癞揉、遍歷過程中,子視圖調(diào)用hitTest:withEvent:方法溺欧,如果返回不為nil喊熟,則將當(dāng)前子視圖作為事件響應(yīng)視圖,返回給調(diào)用方姐刁。否則芥牌,繼續(xù)遍歷其它子視圖
5、如果沒有找到子視圖聂使,由于點(diǎn)擊位置在當(dāng)前視圖范圍內(nèi)壁拉,則會(huì)把當(dāng)前視圖作為事件響應(yīng)視圖返回給調(diào)用方。
4.4柏靶、擴(kuò)大按鈕的點(diǎn)擊區(qū)域
核心點(diǎn)重寫pointInside:withEvent:方法弃理、在里面寫明point在什么區(qū)域返回YES,什么區(qū)域返回NO即可屎蜓。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat x1 = point.x;
CGFloat y1 = point.y;
CGFloat x2 = self.frame.size.width / 2;
CGFloat y2 = self.frame.size.height / 2;
double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
if (dis <= self.frame.size.width / 2) {
return NO;
}
else{
return YES;
}
}
備注:上面例題產(chǎn)生的效果痘昌,只有點(diǎn)擊區(qū)域大于某個(gè)圓形區(qū)域,才會(huì)有效,否則無效辆苔。
上面講的是事件的傳遞流程算灸,這里講的是事件的響應(yīng)流程。
五驻啤、響應(yīng)鏈
5.1菲驴、響應(yīng)事件流程
1、點(diǎn)擊UILabel骑冗、UITextField赊瞬、UIButton后,它們的下一個(gè)響應(yīng)者是UIView(容器)贼涩。
2巧涧、容器View繼續(xù)傳遞給UIView(可能是UIViewController的View)。如果有UIViewController磁携,則下一個(gè)響應(yīng)者是UIViewController褒侧。
3、如果上面都沒有響應(yīng)者谊迄,則會(huì)傳遞兒UIWindow闷供。
4、UIWindow傳遞給UIApplication统诺。
5歪脏、UIApplication傳遞給UIApplicationDelegate。
簡單總結(jié)如下:First Responser -> The Window ->The
Applicationn->AppDelegate
備注:事件傳遞給UIApplication粮呢,代表這個(gè)事件沒有實(shí)際的響應(yīng)動(dòng)作婿失,響應(yīng)循環(huán)也就結(jié)束了。
5.2啄寡、視圖響應(yīng)事件
//一根或者多根手指開始觸摸view(手指按下)
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
//一根或者多根手指在view上移動(dòng)(隨著手指的移動(dòng)豪硅,會(huì)持續(xù)調(diào)用該方法)
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
//一根或者多根手指離開view(手指抬起)
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
//某個(gè)系統(tǒng)事件(例如電話呼入)打斷觸摸過程
-(void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
總結(jié)
如果問相關(guān)響應(yīng)鏈的問題,可以從下面兩個(gè)方面回答:
1挺物、hitTest尋找命中視圖懒浮。
2、從命中視圖開始识藤,沿著響應(yīng)鏈向上尋找真正的響應(yīng)者砚著。
3、如果最終沒有找到響應(yīng)者痴昧,就會(huì)忽略到這個(gè)事件稽穆,也就是不會(huì)產(chǎn)生實(shí)質(zhì)性的動(dòng)作,不會(huì)引起崩潰赶撰。