iOS 事件响应链

11 篇文章 0 订阅

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
底层具体实现如下 :
- (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;
}

事件传递给窗口或控件的后,就调用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; 

3.相对位置转换函数

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

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

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

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

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

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

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

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

3.响应链使用举例

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

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

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

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

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;
}

4.其他

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

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

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

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

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值