說到iOS中的觸摸事件不得不提到兩個內(nèi)容:一個是UIResponder起愈、一個是事件的響應(yīng)鏈
什么UIResponser敞峭?
Responsder objects是UIResponder的實例化對象铛铁,他組成了事件處理的骨架赶撰,許多關(guān)鍵對象都是UIResponder對象包括UIApplication月帝、UIViewController阱扬、UIView也包括UIWindow。
也就是說只要繼承UIResponder的類都可以相應(yīng)事件捻撑。
一般我們通過重寫UIResponder的四個方法來處理事件
// 一根或者多根手指開始觸摸view
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
// 一根或者多根手指在view上移動磨隘,【會多次調(diào)用】
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
// 一根或者多根手指離開view
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
// 觸摸結(jié)束前,某個系統(tǒng)事件(例如電話呼入)會打斷觸摸過程
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
// 提示:touches中存放的都是UITouch對象
響應(yīng)鏈
一個觸摸事件是從用戶點擊屏幕開始 由UIApplication向下分發(fā) 經(jīng)由 UIWindow 在向下傳遞給UIViewController和UIVIew
那么 我們一個UIViewController有那么多View到底事件應(yīng)該交給誰消費掉呢顾患?
有兩個重要的方法
- hitTest:withEvent:
- pointInside:withEvent:
他們的作用分別是
hitTest:withEvent:尋找并返回最合適的view番捂、
pointInside:withEvent: 判斷點是否在當(dāng)前的View上
-
兩個方法調(diào)用順序
我們通過實例驗證一下 新建四個View并重寫他們的touchesXXX方法、hitTest:withEvent: 方法江解、以及pointInside:withEvent:方法并打印方法名和類名
views
//示例 其他View以此為模板
#import "GrayView.h"
@implementation GrayView
- (void)layoutSubviews
{
self.backgroundColor = [UIColor grayColor];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%@ pointInside", NSStringFromClass([self class]));
return [super pointInside:point withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%@ hitTest", NSStringFromClass([self class]));
return [super hitTest:point withEvent:event];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"%@ touchBegan", NSStringFromClass([self class]));
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"%@ touchesMoved",NSStringFromClass([self class]));
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"%@ touchesEnded", NSStringFromClass([self class]));
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"%@ touchesCancelled", NSStringFromClass([self class]));
[super touchesCancelled:touches withEvent:event];
}
@end
//在ViewController中添加他們
//ViewController.m
GrayView *grayView = [[GrayView alloc] initWithFrame:CGRectMake(50.f, 100.f, 260.f, 200.f)];
RedView *redView = [[RedView alloc] initWithFrame:CGRectMake(0.f, 0.f, 120.f, 100.f)];
BlueView *blueView = [[BlueView alloc] initWithFrame:CGRectMake(140.f, 100.f, 100.f, 100.f)];
YellowView *yellowView = [[YellowView alloc] initWithFrame:CGRectMake(50.f, 360.f, 200.f, 200.f)];
[self.view addSubview:grayView];
[grayView addSubview:redView];
[grayView addSubview:blueView];
[self.view addSubview:yellowView];
運行后的效果圖
當(dāng)我們點擊灰色View時设预,控制臺打印如下信息
我們看到最先調(diào)用了YellowView的hitTest方法而后調(diào)用pointInside方法,如果pointInside返回NO說明這個觸摸點不在這個View上就遍歷跟他同一級的下一個View犁河,如果pointInside返回YES說明觸摸點在這個View的范圍內(nèi)鳖枕,則逆序遍歷他的子View,如果子View的pointInside都返回NO那么說明這個觸摸點在當(dāng)前返回YES的這個View上
有幾點回影響hitTest流程當(dāng)
- alpha<=0.01
- userInterActionEnabled = NO
- hidden = YES;
pointInside都會返回NO
我們可以根據(jù)上面的結(jié)論自己寫一下hitTest的內(nèi)部邏輯驗證推論是否正確
//GrayView.m
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
//逆序遍歷子VIew看觸摸點是否在子View中
NSArray *subViews = [[self.subviews reverseObjectEnumerator] allObjects];
UIView *tmpView = nil;
for (UIView *view in subViews) {
CGPoint convertedPoint = [self convertPoint:point toView:view];
if ([view pointInside:convertedPoint withEvent:event]) {
tmpView = view;
break;
}
}
if (tmpView) {
//在子View中
return tmpView;
} else if([self pointInside:point withEvent:event]) {
//點不在子View中而在自己身上
return self;
} else {
//觸摸點不在當(dāng)前View上
return nil;
}
}
我們依然點擊GrayView發(fā)現(xiàn)依然可以找到正確被點擊的View
現(xiàn)在我們給RedView的hitTest方法打一個斷點看并點擊RedView查看調(diào)用層級