通过拦截 hitTest:withEvent 方法,解决超出父视图的子视图不能接受点击事件的问题

由于项目需求,我需要在一个高度为50的控件上面创建一个下拉菜单,效果如下


screenshot.png

当我做完之后发现,下拉菜单的下拉选择项不能点击


screenshot.png

这是因为我们的控件高度只有50,但是下拉菜单的高度超出了控件的大小,这样,我们就接受不到点击事件了
这边找了一个比较详细的图,来描述事件的分发
226702-dd53b5a6df2f3ea5.png

每个 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,这样我们的下拉菜单就能响应点击了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,076评论 25 708
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,200评论 4 61
  • 如果你感到委屈,证明你还有底线 。如果你感到迷茫,证明你还有追求 。如果你感到痛苦,证明你还有力气 。如果你感到绝...
    伴路阅读 244评论 0 0
  • 凯文·凯利今年6月又被邀请到TED做演讲了,这次他讲的题目是“How AI can bring on a seco...
    琢爱舟阅读 1,748评论 3 8
  • 拨动青春的琴弦 曾经带过一个学生,少年性格寡默沉腼,不喜与人说话,学业成绩中等偏下,每天在教室里静静的坐着,好像一...
    风起123阅读 422评论 0 1