- 当一个view被点击了,是通过什么样的机制知道是该view被点击的呢?
- 如果父视图上存在两个视图,并且这两个视图都可以被点击,我们怎么选择性的跳过第一个视图,直接相应第二视图呢?(第一个视图盖在第二个视图上面,这种情况在开发中有时候也会遇到)
当然我们iOS设备的屏幕,UIKit就会生成一个事件对象UIEvent。UIApplication获取到UIEvent之后,会通过hitTest方法来决定把UIEvent传递给谁。hitTest的作用就是找出触摸点下面的view是什么(first Responder)。
示意图.png假设点击了 C-3,流程如下。
1.因为触摸点在A里面,检查subviews,B-1和B-2。
2.因为触摸点在B-2,里面检查B-2的subviews,C-2和C-3。
3.因为触摸点在C-3里面,检查C-3的subviews,没有,返回C-3。这时候C-3就是hitTest View,就是第一响应者。
以上步骤回答了问题1
Tips:
- hitTest的顺序是从A-B-C,而不是从第一个点击的view开始。这个很容易误会。
- 如果点击没有发生在B-1中,那么也不会检测C-1。
- 如果你的Subview设置了clipsToBounds=NO,实际显示区域可能超出了superView的frame,你点击超出的部分,是不会处理你的事件的。
UIView提供两个方法来确定Firstr Responder
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
-
当一个View收到hitTest消息时,会调用自己的pointInside:withEvent:方法,如果pointInside返回YES,则表明触摸事件发生在我自己内部,否则会遍历自己的所有Subview去寻找最小单位(没有任何子view)的UIView。
-
如果当前View.userInteractionEnabled = NO,enabled=NO(UIControl),或者alpha<=0.01, hidden等情况的时候,hitTest就不会调用自己的pointInside了,直接返回nil,然后系统就回去遍历兄弟节点。
代码如下
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden)
{
return nil;
}
BOOL inside = [self pointInside:point withEvent:event];
UIView *hitView = nil;
if (inside)
{
NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
for (UIView *subview in enumerator)
{
hitView = [subview hitTest:point withEvent:event];
if (hitView)
{
break;
}
}
if (!hitView)
{
hitView = self;
}
return hitView;
}
else {
return nil;
}
}
通过上面的方法可以控制谁是First Responder,回答了问题二。
hit-Test 是事件分发的第一步,就算你的app忽略了事件,也会发生hit-Test。确定了hit-TestView之后,才会开始进行下一步的事件分发
网友评论