• iOS 事件响应链


    1.事件的分发

    在iOS中, 只有继承了UIResponder的类的对象才能接收并处理事件,我们称为响应者对象
    UIApplication,UIViewController,UIView均为响应者对象, 都能够接收并处理事件。事件的分发,只会在他们之间进行。
    默认的事件分发的过程
    当用户点击了一个UIResponder:
    1.系统会将此次触发事件加入到一个由UIApplication管理的队列事件中
    2.UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常会先发送事件给应用程序的主窗口(keyWindow)
    3.主窗口会判断能否处理事件,如果可以,开始在自己的根View上寻找处理事件的View
    4.根View在自己的子View上找处理事件的子VIew(从最后一个添加的子View开始处理),如果找到,事件就交由他处理,响应链终止,如果没有就自己处理响应事件。可以处理事件的VIew的判断满足两点:该view能否处理事件;点击事件在VIew区域内。
    5.反复步骤4,如果没有最适合处理事件的VIew就将事件交给KeyWindow处理

    2.响应链传递相关函数

    1.hitTest:withEvent:

       只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法寻找处理事件的View,返回的就是处理事件的View
    
    • 1
    底层具体实现如下 :
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        // 当前view能否接收事件(如果他能接听,那么就一定是当前view或者他的子view处理掉这个事件,而不会继续向外抛出事件了)
        if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
            return nil;
        }
        // 判断点在不在当前view范围内
        if ([self pointInside:point withEvent:event] == NO) { 
            return nil;
        }
        // 从后往前遍历子view(后加的子view优先响应,因为他在更上层)
        NSInteger count = self.subviews.count;
        for (NSInteger i = count - 1; i >= 0; i--) {
            UIView *childView = self.subviews[i];
            // 把当view上的坐标系转换成子view上的坐标系
            CGPoint childPoint = [self convertPoint:point toView:childView];
            UIView *hitView = [childView hitTest:childPoint withEvent:event];
            if (hitView) { // 有可响应的view
                return hitView;
            }
        }
        // 检查子view没有可以响应的,那么就让自己响应
        return self;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    事件传递给窗口或控件的后,就调用hitTest:withEvent:方法寻找更合适的view。所以是,先传递事件,再根据事件在自己身上找更合适的view。不管子控件是不是最合适的view,系统默认都要先把事件传递给子控件,经过子控件调用自己的hitTest:withEvent:方法验证后才知道有没有更合适的view。即便父控件是最合适的view了,子控件的hitTest:withEvent:方法还是会调用,不然怎么知道有没有更合适的!即,如果确定最终父控件是最合适的view,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的。
    hitTest:withEvent:方法忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。
    如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。

    pointInside: withEvent:

    该方法判断触摸点是否在控件身上, 是则返回YES, 否则返回NO. 当点击事件在view范围内,默认返回YES

    - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; 
    
    
    • 1
    • 2

    3.相对位置转换函数

    将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值

    - (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;
    
    • 1

    将像素point从view中转换到当前视图中,返回在当前视图中的像素值

    - (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;
    
    • 1

    将rect由rect所在视图转换到目标视图view中,返回在目标视图view中的rect

    - (CGRect)convertRect:(CGRect)rect toView:(UIView *)view;
    
    • 1

    将rect从view中转换到当前视图中,返回在当前视图中的rect

    - (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;
    
    • 1

    3.响应链使用举例

    因为点击事件的传递,就是由控件能否处理事件和点击位置是否为处理位置决定,所以重写以上两个函数,可以实现:
    点击视图1,由视图2来响应
    让子控件位于父控件之外的部分,响应事件

    1.点击视图1由视图2来响应

    view1 和view2为同一父视图下不重叠的两个子视图

    //view1 中的代码
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        return _view2;
    }
    
    • 1
    • 2
    • 3
    • 4

    2.让子视图位于父视图之外的部分响应事件

    重写父view 的hitTest: withEvent: 函数

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        
        for(int i=0;i<self.subviews.count;i++){
            UIView *view = self.subviews[i];
            CGPoint subPoint = [self convertPoint:point toView:view];
            if([view pointInside:subPoint withEvent:event]){
                return view;
            }
        }
        return self;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.其他

    为什么手势和单击事件只会响应手势?

    UIGestureRecognizer 有个属性cancelsTouchesInView,这个属性默认值是YES,即当手势识别成功后,会发送touchesCancelled消息给view来结束view的响应。
    如果cancelsTouchesInView为NO,那么gestureRecognizer和view都可以响应

    UIGestureRecognizer 有个属性cancelsTouchesInView,这个属性默认值是YES,即当手势识别成功后,会发送touchesCancelled消息给view来结束view的响应。
    
    • 1

    UIView不能接收触摸事件的三种情况

    不接受用户交互:userInteractionEnabled = NO;
    隐藏:hidden = YES;
    透明:alpha = 0.0~0.01

  • 相关阅读:
    Transfer principle
    java题库——认证考试题1
    数据结构之B树
    正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-24.5,6 SPI驱动实验-ICM20608 ADC采样值
    深度学习交通车辆流量分析 - 目标检测与跟踪 - python opencv 计算机竞赛
    idea将jar包deploy到本地仓库
    深度探索:智能家居背后的科技力量与伦理思考
    nginx配置IP白名单
    shell编写循环检查脚本
    第6章:表单中的受控组件与非受控组件
  • 原文地址:https://blog.csdn.net/htwhtw123/article/details/125461469