流程
-
点击一个UIView或产生一个触摸事件A,这个触摸事件A会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。
-
UIApplication会从事件对列中取出最前面的事件(此处假设为触摸事件A),把事件A传递给应用程序的主窗口(keyWindow)。
-
窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。
看A,B两个效果图体会下这个流程
A:
B:
怎么找到合适的View流程
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
是否允许接收触摸事件
该hitTest:withEvent:方法首先检查是否允许视图接收触摸。如果出现以下情况,则允许视图接收触摸:
- 视图未隐藏:
self.hidden == NO
- 该视图启用了用户交互:
self.userInteractionEnabled == YES
- 视图的alpha级别大于0.01:
self.alpha > 0.01
- 该视图包含以下内容:
pointInside:withEvent: == YES
常见用法
- 比如上图这个按钮太小,我手指可能点击不到,不增加icon的大小情况下,增加额外的触摸。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
CGRect touchRect = CGRectInset(self.bounds, -10, -10);
if (CGRectContainsPoint(touchRect, point)) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
2.比如把事件传递到被覆盖的下面的视图,重写覆盖视图的hitTest方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitTestView = [super hitTest:point withEvent:event];
if (hitTestView == self) {
hitTestView = nil;
}
return hitTestView;
}
3.将事件传递给子视图,如下图子视图占据大部分空间,一些手势在边缘地方也就是UIView里,也应该响应到UIScrollView中,重写UView方法。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitTestView = [super hitTest:point withEvent:event];
if (hitTestView) {
hitTestView = self.scrollView;
}
return hitTestView;
}