節(jié)選自:http://www.superqq.com/blog/2015/04/23/iosyong-hu-dian-ji-shi-jian-chu-li/?utm_source=tuicool&utm_medium=referral
#處理機(jī)制
iOS事件處理,首先應(yīng)該是找到能處理點(diǎn)擊事件的視圖,然后在找到的這個(gè)視圖里處理這個(gè)點(diǎn)擊事件。
處理原理如下:
? 當(dāng)用戶點(diǎn)擊屏幕時(shí),會(huì)產(chǎn)生一個(gè)觸摸事件,系統(tǒng)會(huì)將該事件加入到一個(gè)由UIApplication管理的事件隊(duì)列中
? UIApplication會(huì)從事件隊(duì)列中取出最前面的事件進(jìn)行分發(fā)以便處理,通常,先發(fā)送事件給應(yīng)用程序的主窗口(UIWindow)
? 主窗口會(huì)調(diào)用hitTest:withEvent:方法在視圖(UIView)層次結(jié)構(gòu)中找到一個(gè)最合適的UIView來(lái)處理觸摸事件
(hitTest:withEvent:其實(shí)是UIView的一個(gè)方法,UIWindow繼承自UIView,因此主窗口UIWindow也是屬于視圖的一種)
? hitTest:withEvent:方法大致處理流程是這樣的:
首先調(diào)用當(dāng)前視圖的pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi):
? 若pointInside:withEvent:方法返回NO,說(shuō)明觸摸點(diǎn)不在當(dāng)前視圖內(nèi),則當(dāng)前視圖的hitTest:withEvent:返回nil
? 若pointInside:withEvent:方法返回YES,說(shuō)明觸摸點(diǎn)在當(dāng)前視圖內(nèi),則遍歷當(dāng)前視圖的所有子視圖(subviews),調(diào)用子視圖的hitTest:withEvent:方法重復(fù)前面的步驟,子視圖的遍歷順序是從top到bottom,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖的hitTest:withEvent:方法返回非空對(duì)象或者全部子視圖遍歷完畢:
? 若第一次有子視圖的hitTest:withEvent:方法返回非空對(duì)象,則當(dāng)前視圖的hitTest:withEvent:方法就返回此對(duì)象,處理結(jié)束
? 若所有子視圖的hitTest:withEvent:方法都返回nil,則當(dāng)前視圖的hitTest:withEvent:方法返回當(dāng)前視圖自身(self)
? 最終,這個(gè)觸摸事件交給主窗口的hitTest:withEvent:方法返回的視圖對(duì)象去處理。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%@--hitTest",[self class]);
NSLog(@"%@", self.subviews);
// 1.判斷當(dāng)前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判斷點(diǎn)在不在當(dāng)前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.從后往前遍歷自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
// 把當(dāng)前控件上的坐標(biāo)系轉(zhuǎn)換成子控件上的坐標(biāo)系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 尋找到最合適的view
return fitView;
}
}
// 循環(huán)結(jié)束,表示沒(méi)有比自己更合適的view
return self;
}
例子:
層次: 黃色view在上面,按鈕在下面
通過(guò)hitTest:withEvent:方法可以設(shè)置, 當(dāng)點(diǎn)擊按鈕時(shí),事件被button接收,而不是黃色view
#import "YellowView.h"
@interface YellowView ()
@property (nonatomic, weak) IBOutlet UIButton *btn;
@end
@implementation YellowView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 當(dāng)前坐標(biāo)系上的點(diǎn)轉(zhuǎn)換到按鈕上的點(diǎn)
CGPoint btnP = [self convertPoint:point toView:self.btn];
// 判斷點(diǎn)在不在按鈕上
if ([self.btn pointInside:btnP withEvent:event]) {
// 點(diǎn)在按鈕上
return self.btn;
}else{
return [super hitTest:point withEvent:event];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%@",self.btn);
NSLog(@"%s",__func__);
}
@end