iOS 响应者链的原理、事件传递

iOS事件处理与响应者链
本文详细介绍了iOS系统的事件处理机制,包括触屏事件、运动事件和远程控制事件等。深入探讨了响应者链的概念及其工作原理,通过具体代码示例解释了如何通过重写hitTest:withEvent:方法来解决特定场景下的事件响应问题。

hittest inview:方法https://www.jianshu.com/p/d8512dff2b3e

原理解释:https://www.jianshu.com/p/b0884faae603

cell上放scrollview,cell点击不响应的解决:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    /**
     *  此注释掉的方法用来判断点击是否在父View Bounds内,
     *  如果不在父view内,就会直接不会去其子View中寻找HitTestView,return 返回
     */
//    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView ==self) {
                return hitTestView;
            }
        }
        return self;
//    }
}
//重写该方法后可以让超出父视图范围的子视图响应事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    if (view == nil) {
        for (UIView *subView in self.subviews) {
            CGPoint tp = [subView convertPoint:point fromView:self];
            if (CGRectContainsPoint(subView.bounds, tp)) {
                view = subView;
            }
        }
    }
    return view;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    CGPoint tempPoint = [self.btn convertPoint:point fromView:self];
    if ([self.btn pointInside:tempPoint withEvent:event]) {
        return self.btn;
    }
    return view;
}

https://blog.youkuaiyun.com/u010462316/article/details/76572756

//点击按钮的时候,不让按钮响应,而让当前的cell响应。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if ([view isKindOfClass:[UIButton class]]) {
        return self;
    }
    return [super hitTest:point withEvent:event];
}

加速计事件

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

    远程控制事件

- (void)remoteControlReceivedWithEvent:(UIEvent *)event;

触摸的几个事件

//手指按下

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

   }

//手指移动

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

}

//手指抬起

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

}

//触摸取消

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

}

简单来说就是 :一级一级的找到响应的视图,如果没有就传给UIWindow实例和UIApplication实例,要是他们也处理不了,就丢弃这次事件... 

对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:

1、触屏事件(Touch Event)

2、运动事件(Motion Event)

3、远端控制事件(Remote-Control Event)

为什么用队列管理事件,而不用栈?

队列先进先出,能保证先产生的事件先处理。栈先进后出。

响应者链条概念: iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。 

响应者对象(Responder Object) 指的是  有响应和处理事件能力的对象。 响应者链就是由一系列的响应者对象 构成的一个层次结构。

UIResponder 是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的  UIApplication、 UIViewController、 UIWindow 和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。

UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。


hitTest:withEvent:方法的处理流程如下: 
首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;  
若返回NO,则hitTest:withEvent:返回nil;  
若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;  
若第一次有子视图返回空对象,则hitTest:withEvent:方法返回此对象,处理结束;  
如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。  

假如用户点击了View E,下面结合图二介绍hit-test view的流程:

1、A是UIWindow的根视图,因此,UIWindwo对象会首相对A进行hit-test;

2、显然用户点击的范围是在A的范围内,因此,  pointInside:withEvent:返回了YES,这时会继续检查A的子视图;

3、这时候会有两个分支,B和C:

点击的范围不再B内,因此B分支的  pointInside:withEvent:返回NO,对应的 hitTest:withEvent:返回nil;

点击的范围在C内,即C的  pointInside:withEvent:返回YES;

4、这时候有D和E两个分支:

点击的范围不再D内,因此D  的 pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;

点击的范围在E内,即E的 pointInside:withEvent:返回YES,由于E没有子视图(也可以理解成对E的子视图进行hit-test时返回了nil),因此,E的  hitTest:withEvent: 会将E返回,再往回回溯,就是C的  hitTest:withEvent:返回E--->>A的  hitTest:withEvent:返回E。 

至此,本次点击事件的第一响应者就通过响应者链的事件分发逻辑成功的找到了。

不难看出,这个处理流程有点类似二分搜索的思想,这样能以最快的速度,最精确地定位出能响应触摸事件的UIView。

***上面找到了事件的第一响应者,接下来就该沿着寻找第一响应者的相反顺序来处理这个事件,如果UIWindow单例和UIApplication都无法处理这一事件,则该事件会被丢弃。***

上一个响应者(nextResponder)是谁?

  判断步骤:
  1>如果当前的view是控制器的view,控制器就是上一个响应者
  2>如果当前的view不是控制器的view,那么父控件就是上一个响应者
  3>如果当前响应者是控制器,那么上一个响应者是UIWindow;如果UIWindow也不处理,就再往上传给UIApplication
  4>如果UIApplication也不处理,那么这条消息就被会废弃

说明:

1、如果最终  hit-test没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃;

2、hitTest:withEvent:方法将会忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。

转载自http://www.cnblogs.com/mcj-coding/p/3569908.html

======设置超出scrollview的部分可以响应======

在scrollview中添加一下代码:----scrollview最好建一个类独立出来

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{

    

//    UIView *view = [super hitTest:point withEvent:event];

    

    UIView *view;

    UIView *tempView;

    

    tempView = [selfgetTargetView:selfpoint:point event:event];

    

    view = tempView;

    

    

    

    

    return view;

}

- (UIView *)getTargetView:(UIView *)view

                    point:(CGPoint)point

                    event:(UIEvent *)event

{

    

    __blockUIView *subView;

    

    //逆序由层级最低也就是最上层的子视图开始

    [view.subviewsenumerateObjectsWithOptions:NSEnumerationReverseusingBlock:^(__kindofUIView * _Nonnull obj,NSUInteger idx, BOOL *_Nonnull stop) {

        //point 从view转到 obj中

        CGPoint hitPoint = [objconvertPoint:point fromView:view];

        //        NSLog(@"%@ - %@",NSStringFromCGPoint(point),NSStringFromCGPoint(hitPoint));

        

        if([objpointInside:hitPoint withEvent:event])//在当前视图范围内

        {

            if(obj.subviews.count != 0)

            {

                //如果有子视图递归

                subView = [selfgetTargetView:obj point:hitPoint event:event];

                

                if(!subView)

                {

                    //如果没找到提交当前视图

                    subView = obj;

                }

            }

            else

            {

                subView = obj;

            }

            

            *stop = YES;

        }

        else//不在当前视图范围内

        {

            if(obj.subviews.count != 0)

            {

                //如果有子视图递归

                subView = [selfgetTargetView:obj point:hitPoint event:event];

            }

        }

        

    }];

    

    return subView;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值