iOS中三種事件類型
- 觸屏事件(Touch Event)
- 運(yùn)動(dòng)事件(Motion Event)
- 遠(yuǎn)端控制事件(Remote-Control-Event)
響應(yīng)者對(duì)象(Responder Object)
響應(yīng)者對(duì)象指的是有響應(yīng)和處理上述3種事件能力的對(duì)象程拭。響應(yīng)者鏈就是由一系列響應(yīng)者對(duì)象構(gòu)成一個(gè)層次結(jié)構(gòu)。
UIResponder
是所有響應(yīng)對(duì)象的基類应役,在UIResponder
類中定義了處理上述各種事件的接口赖瞒。我們熟悉的UIApplication外里、UIWindow窃植、UIViewController领铐、UIView
都直接或間接繼承自UIResponder
悯森,所以它們都是可以構(gòu)成響應(yīng)者鏈的響應(yīng)者對(duì)象。
響應(yīng)鏈的傳遞
盜圖真香
這張圖清晰的解釋了響應(yīng)鏈的傳遞過(guò)程:
- 當(dāng)發(fā)生觸屏事件后绪撵,系統(tǒng)會(huì)將事件加到
UIApplication
管理的一個(gè)任務(wù)隊(duì)列中瓢姻,并將事件分發(fā)下去。 - 通常先發(fā)送給
keyWindow
音诈,UIWindow
繼續(xù)在其視圖層次結(jié)構(gòu)中找到一個(gè)最合適的視圖來(lái)處理事件幻碱。 -
UIWindow
會(huì)在它視圖上調(diào)用hitTest:withEvent:
方法续膳,hitTest:withEvent
又會(huì)調(diào)用自身的pointInside:
方法,若返回YES收班,說(shuō)明點(diǎn)擊區(qū)域在UIWindow
范圍內(nèi)坟岔,然后UIWindow
遍歷它子視圖(后添加的子視圖先遍歷)調(diào)用hitTest:WithEvent:
方法。 - 上圖
UIWindow
遍歷子視圖MainView
摔桦,MainView
調(diào)用自身hitTest:withEvent
方法社付,且pointInside:
方法返回YES,繼續(xù)遍歷子視圖ViewC
邻耕。 -
ViewC
調(diào)用自身hitTest:withEvent:
方法鸥咖,結(jié)果發(fā)現(xiàn)pointInside:
方法返回NO,hitTest:
方法返回nil兄世;輪到ViewB
啼辣。 -
ViewB
調(diào)用自身hitTest:withEvent:
方法,結(jié)果發(fā)現(xiàn)pointInside:
方法返回YES御滩,繼續(xù)遍歷子視圖ViewB.2 ViewB.1
鸥拧。 - 遍歷到
ViewB.1
無(wú)子視圖可以遍歷,遍歷終止削解,hitTest:
方法中返回自身即ViewB.1
富弦。 - 到此響應(yīng)鏈結(jié)束,
ViewB.1
響應(yīng)了事件氛驮。
模擬系統(tǒng)的調(diào)用過(guò)程
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if (self.userInteractionEnabled
&& !self.hidden
&& [self pointInside:point withEvent:event]) {
// 使用reverseObjectEnumerator進(jìn)行倒序遍歷
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
// 將像素point從view中轉(zhuǎn)換到當(dāng)前視圖中腕柜,返回在當(dāng)前視圖中的像素值
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *responseView = [subview hitTest:convertedPoint withEvent:event];
if (responseView) {
return responseView;
}
}
//無(wú)子視圖返回自身
return self;
}
return nil;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
//判斷點(diǎn)擊位置是否在視圖范圍內(nèi)
if (CGRectContainsPoint(self.bounds, point)) {
return YES;
}
return NO;
}
解決實(shí)際問(wèn)題
1、響應(yīng)超出父視圖外區(qū)域的事件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
if (CGRectContainsPoint(self.bounds, point)) {
return YES;
}
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
// 將像素point從view中轉(zhuǎn)換到當(dāng)前視圖中矫废,返回在當(dāng)前視圖中的像素值
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
BOOL inside = [subview pointInside:convertedPoint withEvent:event];
if (inside) {
return YES;
}
}
return NO;
}
2盏缤、面試題superView上添加viewA,viewA上添加viewB蓖扑,viewB上添加viewC唉铜,且B、C都不在各自視圖內(nèi)赵誓。此時(shí)重寫viewB的pointInside:方法并返回YES打毛,點(diǎn)擊A和點(diǎn)擊B分別響應(yīng)哪個(gè)視圖的事件。
[viewSuper addSubview:viewA];
[viewA addSubview:viewB];
[viewB addSubview:viewC];
UITapGestureRecognizer *tapA = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapA:)];
[viewA addGestureRecognizer:tapA];
UITapGestureRecognizer *tapB = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapB:)];
[viewB addGestureRecognizer:tapB];
UITapGestureRecognizer *tapC = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapC:)];
[viewC addGestureRecognizer:tapC];
UITapGestureRecognizer *tapS = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapS:)];
[viewSuper addGestureRecognizer:tapS];
- (void)tapA:(UITapGestureRecognizer *)tap {
NSLog(@"tapA");
}
- (void)tapB:(UITapGestureRecognizer *)tap {
NSLog(@"tapB");
}
- (void)tapC:(UITapGestureRecognizer *)tap {
NSLog(@"tapC");
}
- (void)tapS:(UITapGestureRecognizer *)tap {
NSLog(@"tapSuper");
}
1616056611363.jpg
答:點(diǎn)擊A俩功,響應(yīng)事件B,打印tapB
解:
- 先viewSuper調(diào)用
hitTest:
方法并且pointInside:
返回YES碰声; - 遍歷子視圖ViewA诡蜓,ViewA調(diào)用
hitTest:
并且點(diǎn)在范圍內(nèi)pointInside:
返回YES; - 遍歷子視圖ViewB胰挑,ViewB調(diào)用
hitTest:
雖然點(diǎn)不在范圍內(nèi)蔓罚,但pointInside:
返回YES椿肩; - 接著遍歷ViewC,點(diǎn)擊的點(diǎn)不在ViewC范圍內(nèi)
pointInside:
返回NO豺谈; - ViewB的
hitTest:
返回自身郑象;所以響應(yīng)了事件B;
答:點(diǎn)擊B茬末,響應(yīng)事件Super厂榛,打印tapSuper
解: - 先viewSuper調(diào)用
hitTest:
方法并且pointInside:
返回YES; - 遍歷子視圖ViewA丽惭,因?yàn)閂iewB上的點(diǎn)不在ViewA范圍內(nèi)击奶,所以
pointInside:
返回NO; - viewSuper的
hitTest:
返回自身责掏;所以響應(yīng)了事件Super柜砾;