设计师在设计UI界面时,为了更加有个性通常会把界面设计的比较复杂实现。比如下面这个界面,中间凸起的那个发布按钮。

iOS开发 超出父控件点击无响应 ios 超出父视图 点击_objective-c

默认情况下,如果点击图片红色区域那块是没有任何响应的,系统会丢弃调这次触摸事件。那我们要怎么满足即使点击红色区域也响应发布按钮的点击事件呢?
首先我们必须了解事件的传递过程

  1. 当产生一触摸事件,这个触摸事件会被添加到UIApplication管理的事件队列中,所以首先接收到事件的是UIApplication。
  2. 由于队列的先进先出特性,UIApplication会取出最前面的事件传递给应用程序的主窗口也就是keyWindow。
  3. 让后在keyWindow里面找到最合适的响应者(view)来处理这个触摸事件

那么又是如何找到最合适的响应者(view)呢?

  1. 首先判断主窗口(keyWindow)自己是否能接受触摸事件
  2. 触摸点是否在自己身上
  3. 从后往前遍历子控件,重复前面的两个步骤
  4. 如果没有符合条件的子控件,那么就认为自己最合适处理

系统是通过下面两个核心的方法来查找的

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

所以我们就可通过重写以上的方法来达到我们的目的。我们通过一个例子来验证一下,如下图结构

iOS开发 超出父控件点击无响应 ios 超出父视图 点击_xcode_02

橘色的按钮是添加在底部绿色的view里面,但是有一部分超出了绿色view的范围。默认情况下点击红色区域的按钮部分,这个橘色的按钮是不会有任何响应的。接下来我通过重写绿色view的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event方法来让点击橘色按钮任何一个地方都能响应。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.userInteractionEnabled == NO || self.isHidden || self.alpha <= 0.01) {
        //这三种情况不能响应事件
        return nil;
    }
    //往前遍历子控件
    for (NSInteger i = self.subviews.count - 1; i >= 0; i--) {
        UIView *subView = self.subviews[i];
        //把点的坐标转换成子控件的坐标
        CGPoint newPoint = [self convertPoint:point toView:subView];
        if ([subView hitTest:newPoint withEvent:event]) {
            //找到最合适的view
            return subView;
        }
    }
    return [super hitTest:point withEvent:event];
}

然后通个控制台的打印信息,我们可以看到点击了橘色按钮任意一个地方都能响应点击事件,这样子就实现了按钮超出父控件部分任能响应点击事件。

iOS开发 超出父控件点击无响应 ios 超出父视图 点击_objective-c_03

接下就是怎么调节按钮自身响应事件的范围。通过重写自身的- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event来实现。

  • 扩大范围
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect bounds = self.bounds;
    bounds = CGRectInset(bounds, -50, -50);
    return CGRectContainsPoint(bounds, point);
}

这样子点击红色区域任意位置依然能够响应点击事件

iOS开发 超出父控件点击无响应 ios 超出父视图 点击_ios_04

  • 缩小范围
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect bounds = self.bounds;
    bounds = CGRectOffset(self.bounds, 50, 50);
    return CGRectContainsPoint(bounds, point);
}

iOS开发 超出父控件点击无响应 ios 超出父视图 点击_swift_05


只有点击红色区域内才能响应点击事件