1、寻找触摸的和点击的发生的View
,这个View
被称为hit-testView
。找到hit-test View
的这个过程叫个hit-testing
。
苹果寻找的过程如下:
1、从视图层级别最低的开始,UIwindow开始遍历它的子View,看触摸事件是否在subView
UIView提供两个方法来确定hit-TestView:
// 返回一个 hit-TestView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
// 判断触摸点是否在 view 中
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
举个例子,我们一起来寻找hit-TestView
<UIView:>
| <RedView>
| | <BlueView:>
| | <YellowView:>
分别在RedView 、BlueView、YellowView类中实现以下两个方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"%@ -- %s in",[self class], __FUNCTION__);
UIView * view = [super hitTest:point withEvent:event];
NSLog(@"%@ -- %s out %@",[self class], __FUNCTION__,view);
return view;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
NSLog(@"%@ -- %s in",[self class], __FUNCTION__);
BOOL isInside = [super pointInside:point withEvent:event];
NSLog(@"%@ -- %s out %@",[self class], __FUNCTION__,@(isInside));
return isInside;
}
点击blueView日志如下:
RedView -- -[RedView hitTest:withEvent:] in
RedView -- -[RedView pointInside:withEvent:] in
RedView -- -[RedView pointInside:withEvent:] out 1
YellowView -- -[YellowView hitTest:withEvent:] in
YellowView -- -[YellowView pointInside:withEvent:] in
YellowView -- -[YellowView pointInside:withEvent:] out 0
YellowView -- -[YellowView hitTest:withEvent:] out (null)
BlueView -- -[BlueView hitTest:withEvent:] in
BlueView -- -[BlueView pointInside:withEvent:] in
BlueView -- -[BlueView pointInside:withEvent:] out 1
BlueView -- -[BlueView hitTest:withEvent:] out <BlueView: 0x7fb50ad04270; frame = (10 50; 150 150); layer = <CALayer: 0x604000032e40>>
RedView -- -[RedView hitTest:withEvent:] out <BlueView: 0x7fb50ad04270; frame = (10 50; 150 150); layer = <CALayer: 0x604000032e40>>
通过上面的日志分析我们得到 hit-TestView
是BlueView
分析:触摸BlueView
的时,遍历UIview
的subView
,触摸点在RedView
视图上,再遍历RedView
的自视图YellowView
,触摸点不在YellowView
上,RedView
的子视图BlueView
,触摸点在BlueView
上,BlueView没有子视图,因此BlueView
就是hit-TestView
点击blueView日志如下:
RedView -- -[RedView hitTest:withEvent:] in
RedView -- -[RedView pointInside:withEvent:] in
RedView -- -[RedView pointInside:withEvent:] out 1
YellowView -- -[YellowView hitTest:withEvent:] in
YellowView -- -[YellowView pointInside:withEvent:] in
YellowView -- -[YellowView pointInside:withEvent:] out 1
YellowView -- -[YellowView hitTest:withEvent:] out <YellowView: 0x7fb50ad06f50; frame = (254 50; 150 150); layer = <CALayer: 0x604000033180>>
RedView -- -[RedView hitTest:withEvent:] out <YellowView: 0x7fb50ad06f50; frame = (254 50; 150 150); layer = <CALayer: 0x604000033180>>
通过上面的日志分析我们得到 hit-TestView
是YellowView
分析同上
找到 hit-TestView
之后,事件就交给它来处理,hit-TestView
就是firstResponder
(第一响应者),如果它无法响应事件(不处理事件),则把事件交给它的 nextResponder
(下一个响应者),直到有处理事件的响应者或者结束(传递到 AppDelegate 为止)。这一系列的响应者和事件的传递方向就是响应链(很形象)。在响应链中,所有响应者的基类都是 UIResponder
,也就是说所有可以响应事件的类都是UIResponder
的子类,UIApplication/UIView/UIViewController
都是 UIResponder
的子类。
说了这么多,然并卵,能解决上面问题呢?
在BlueView
上添加一个灰色的button
,且超过BlueView
范围,点击button
(超过blueview
部分的),发现不会响应button
的点击事件。这是为什么呢?
当我们查找到BlueView
视图时,发现button
点击的部分,并不在BlueView
上,所以响应不了事件
重写- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 先使用默认的方法来寻找 hit-TestView
UIView *result = [super hitTest:point withEvent:event];
// 如果 result 不为 nil,说明触摸事件发生在 tabbar 里面,直接返回就可以了
if (result) {
return result;
}
// 到这里说明触摸事件不发生在 tabBar 里面
// 这里遍历那些超出的部分就可以了,不过这么写比较通用。
for (UIView *subview in self.subviews) {
// 把这个坐标从tabbar的坐标系转为subview的坐标系
CGPoint subPoint = [subview convertPoint:point fromView:self];
result = [subview hitTest:subPoint withEvent:event];
// 如果事件发生在subView里就返回
if (result) {
return result;
}
}
return nil;
}
这样再超出blueView
中点击button
,也能响应点击事件