设计师在设计UI界面时,为了更加有个性通常会把界面设计的比较复杂实现。比如下面这个界面,中间凸起的那个发布按钮。
默认情况下,如果点击图片红色区域那块是没有任何响应的,系统会丢弃调这次触摸事件。那我们要怎么满足即使点击红色区域也响应发布按钮的点击事件呢?
首先我们必须了解事件的传递过程。
- 当产生一触摸事件,这个触摸事件会被添加到UIApplication管理的事件队列中,所以首先接收到事件的是UIApplication。
- 由于队列的先进先出特性,UIApplication会取出最前面的事件传递给应用程序的主窗口也就是keyWindow。
- 让后在keyWindow里面找到最合适的响应者(view)来处理这个触摸事件
那么又是如何找到最合适的响应者(view)呢?
- 首先判断主窗口(keyWindow)自己是否能接受触摸事件
- 触摸点是否在自己身上
- 从后往前遍历子控件,重复前面的两个步骤
- 如果没有符合条件的子控件,那么就认为自己最合适处理
系统是通过下面两个核心的方法来查找的
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
所以我们就可通过重写以上的方法来达到我们的目的。我们通过一个例子来验证一下,如下图结构
橘色的按钮是添加在底部绿色的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];
}
然后通个控制台的打印信息,我们可以看到点击了橘色按钮任意一个地方都能响应点击事件,这样子就实现了按钮超出父控件部分任能响应点击事件。
接下就是怎么调节按钮自身响应事件的范围。通过重写自身的- (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);
}
这样子点击红色区域任意位置依然能够响应点击事件
- 缩小范围
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGRect bounds = self.bounds;
bounds = CGRectOffset(self.bounds, 50, 50);
return CGRectContainsPoint(bounds, point);
}
只有点击红色区域内才能响应点击事件