关于响应者链条的只是一找一大堆,不再赘述,这里提一嘴目标视图的查询方式,以及兄弟视图是如何处理的。
首先视图是一个树状结构,有人管这个查找目标视图的过程叫做反向前序深度优先遍历。很贴切的,反向体现在后添加的视图优先搜索。所以兄弟视图覆盖的情况下,会优先响应后添加的视图,下面看HitTest的几个应用。
场景1
当要在主界面上覆盖一个透明视图,并且在透明视图上添加了一个按钮,要求点击按钮时响应,点击透明视图不响应,但是手势要传递给下面的视图。
如上,控制器上覆盖了屏幕等大小的透明视图A,黄色视图添加在A上,红色视图添加在视图的view上,正常情况下,点击黄色视图会有响应,点击红色视图没有响应。如何实现上面的需求呢。透明视图A的代码如下:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event);
if (view == self){
return nil;
}
return view;
}
解释一下:返回的点击目标视图如果不是自己,说明点击的是黄色按钮,返回view,如果是自己当前视图不响应,由于A和红色按钮时兄弟按钮,按照反向前序深度优先遍历原则,会继续遍历A。
场景2
扩大按钮的点击范围
这个需求还是很常见的,产品总是说点不到~~怎么办
-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(UIEdgeInsets)hitTestEdgeInsets {
NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
if(value) {
UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
}else {
return UIEdgeInsetsZero;
}
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
return [super pointInside:point withEvent:event];
}
CGRect relativeFrame = self.bounds;
CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
return CGRectContainsPoint(hitFrame, point);
}
解释下:hitTest通过pointInside方法判断点击是否在当前视图范围,所以我们创建一个UIView的分类,添加一个hitTestEdgeInsets属性,重写pointInside方法,如果hitTestEdgeInsets有值,将当前视图范围按照hitTestEdgeInsets作相应扩展。