iOS 添加透明罩时遮挡后面控件手势的问题

问题:要实现一个简单的夜间模式,简单的方法可以在KeyWindow上加一个半透明的view,但是这个view遮挡住了后面页面的手势了,需要处理一下。这就用到了事件的传递和响应者链。

一、事件的传递和响应者链

一个完整的点击过程包括两个过程,先找到能响应该点击事件的控件,然后通过响应者链响应事件。寻找响应控件是从父控件到子控件的自上而下的过程。

一个点击事件的传递是从父控件往子控件传递的。从UIApplication->keyWindow->子控件。
只要是继承自UIResponder的类(例如UIApplication、UIView、UIViewcontroller)都有两个方法,如下:

//如果当前控件和子控件都能接受事件,则返回子控件,如果子控件不能则返回self,如果都不能则返回nil
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
//判断当前点击的点在不在当前视图范围上
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

事件传递时,从父控件到子控件传递,调用hitTest:withEvent:方法,底层实现如下:

底层具体实现如下 :
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2. 判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    // 3.从后往前遍历自己的子控件,将事件传递给子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        // 把当前控件上的坐标系转换成子控件上的坐标系
        CGPoint childP = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) { 
            // 寻找到响应事件的子控件
            return fitView;
        }
    }
    // 循环结束,表示没有比自己更合适的view
    return self;
}

上一步的意思是从上到下找到合适的响应事件的控件,下一步会调用
改控件的touchesBegan的方法,如果该控件没有实现改方法的话,则将事件传递给父控件响应,如果该控件在touchesBegan方法里调用了[super touchesBegan:touches withEvent:event]方法则改控件和父控件都能响应该事件。

//只要点击控件,就会调用touchBegin,如果没有重写这个方法,自己处理不了触摸事件,将事件传递给父控件响应
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
// 默认会把事件传递给上一个响应者,上一个响应者是父控件,交给父控件处理
[super touchesBegan:touches withEvent:event]; 
// 注意不是调用父控件的touches方法,而是调用父类的touches方法
// super是父类 superview是父控件 
}

由响应者组成的传递事件的链条称为响应者链。
PS:

事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。

二、解决问题

回到开头的问题,在keywindow上添加一个透明罩view,影响后面控件的手势,可将该view的事件传递关闭。
可以自定义一个透明罩view,实现如下两个方法,让透明罩view不继续传递事件,则事件会传递给其他控件,

/**
 让当前view不接受事件
**/
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    return nil;
}
//或者 让点击的点不在当前范围内(造成假象)
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
//{
//    return NO;
//}

事件传递和响应者链
参考博客2

你可能感兴趣的:(OC)