由于项目需求,我需要在一个高度为50的控件上面创建一个下拉菜单,效果如下
当我做完之后发现,下拉菜单的下拉选择项不能点击
这是因为我们的控件高度只有50,但是下拉菜单的高度超出了控件的大小,这样,我们就接受不到点击事件了
这边找了一个比较详细的图,来描述事件的分发
每个 view 都会有
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
return view;
}
这样一个方法
这个方法会判断当前点击的“点”是否在本 view 上,如果在本 view 上,就继续寻找本 view 的 Subview,还是通过此方法判断点击的“点”是否在 Subview 上,直到找完所有的 Subview,然后这个方法就会 return 这个最终的 Subview 并一层层的向上传递给 UIWindow,这样我们就拿到了屏幕上面最终响应的 view。
回到我们最开始遇到的问题。
由于我们下拉菜单超出了我们的自定义控件,当我们点击到下拉菜单时,从 UIWindow 开始通过 hitTest
方法向下寻找响应的 view,当查找到我们的自定义控件时,就会 return 了,因为我们点击的“点”已经超出了自定义控件,也就是说,这个“点”不在我们的自定义控件上,所以在自定义控件上面的下拉菜单无论如何也不会响应。
所以,我们只要手动的去 return 我们的下拉菜单,手动的去连接起这个 响应 view 的链,我们的下拉菜单就能响应
- (UIView *)getTargetView:(UIView *)view point:(CGPoint)point event:(UIEvent *)event
{
__block UIView *subView;
//逆序 由层级最低 也就是最上层的子视图开始
[view.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//point 从view 转到 obj中
CGPoint hitPoint = [obj convertPoint:point fromView:view];
// NSLog(@"%@ - %@",NSStringFromCGPoint(point),NSStringFromCGPoint(hitPoint));
if([obj pointInside:hitPoint withEvent:event])//在当前视图范围内
{
if(obj.subviews.count != 0)
{
//如果有子视图 递归
subView = [self getTargetView:obj point:hitPoint event:event];
if(!subView)
{
//如果没找到 提交当前视图
subView = obj;
}
}
else
{
subView = obj;
}
*stop = YES;
}
else//不在当前视图范围内
{
if(obj.subviews.count != 0)
{
//如果有子视图 递归
subView = [self getTargetView:obj point:hitPoint event:event];
}
}
}];
return subView;
}
这个方法的目的就是找到点击的“点”最终所在的 subview,然后 return。
我们再回到我们的响应链断掉的地方,也就是自定义控件内的 hitTest
方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
//由于响应链在此处断开,我们就去手动寻找最终响应的子视图,传入本 view 遍历本 view 的子视图
UIView *tempview = [self getTargetView:self point:point event:event];
if (tempview) {
view = tempview;
}
return view;
}
手动找到点击的点所在的 subview,并在断开的地方 return,这样我们的下拉菜单就能响应点击了