欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

不要怂一起看看响应者链和事件传递

程序员文章站 2024-03-20 14:54:28
...

在iOS中,只有继承了UIResponder的对象才可以接受并处理时间,这些对象被称为响应者对象

>   UIApplication

>  UIViewController

> UIView

这些都继承自UIResponder,因此他们都是响应者对象,都能够接收并且处理事件,UIResponder提供了以下方法来处理事件

触摸事件

// 开始点击
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;

// 当手指在view上移动的时候 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;

// 当手指离开这个view的时候
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

// 当触摸事件被打断的时候调用(电话打入)
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)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;

-------              当要支持多根手指点击设置属性 : mutableTouch            --------------

事件对象:UIEvent

UIEvent表示用户交互的事件对象,有以下属性

@property(nonatomic,readonly) UIEventType     type;
@property(nonatomic,readonly) UIEventSubtype  subtype;

其中type表示当前响应事件的类型,分别有多点触控、摇一摇、远程操控、3DTouch,在一个用户点击事件处理的过程中,UIEvent对象时唯一的

点击对象:UITouch

UITouch表示单个点击,其类文件中存在枚举类型UITouchPhase的属性,用来表示当前点击的状态。这些状态包括点击开始、移动、停止不动、结束、取消五个状态,每次点击发生的时候,点击对象都放在一个集合中传入UIResponder的回调方法中,我们通过集合中对象获取用户点击的位置

============属性===============

触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow *window;

触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView *view;

短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;

记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;

当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;

============方法===============

// 返回值表示触摸在view上的位置
// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
// 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
-(CGPoint)locationInView:(UIView *)view;

// 该方法记录了前一个触摸点的位置
-(CGPoint)previousLocationInView:(UIView *)view;

-(CGPoint)locationInView:(UIView  *)view   获取当前点击坐标点

-(CGPoint)  previousLocationInView: (UIview *) view 获取上个点击位置的坐标点

除了touch回调的几个点击事件,手势UIGestureRecognizer对象也附加在view上,来实现其他手势事件,在view添加单击手势之后,原来的touchesEnded就无效了

UITouch的作用,保存着跟手指相关的信息,比如位置、时间、阶段,当手指离开时,系统会销毁响应的UITouch对象。

事件的传递和原理

1  >   发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中(为什么是队列而不是栈呢?因为队列是FIFO,也就是先进先出,先产生的事件先处理才对,栈是先进后出的。),UIApplication会从事件中取出最前面的事件,发送给程序的主窗口--->keyWindow,主窗口会在视图层次结构中找到一个最适合的试图来处理事件。

2  >  找到最合适的试图空间后,会调用试图控件的触摸事件的方法

示例:

不要怂一起看看响应者链和事件传递

点击了绿色:UIApplication  --> UIWindow   -->  白色   ---> 绿色

点击了蓝色:UIApplication  --> UIWindow  -->  白色   --->  橙色  ---> 蓝色

如果父控件不能接受触摸事件,那么子控件就不可能接受到触摸事件

一个试图无法接收触摸事件可能是以下情况

1、不接收用户交互

      userInteractionEnabled = NO

2、隐藏

       hidden = yes;

3、透明度

    alpha  = 0;

其中:UIImageView的userInteractionEnable默认为NO,因此UIImageVIew以及它的子控件默认不能接受触摸事件,如果想让某个view不能处理事件,那么就设置这三个属性

    那么怎么找到最合适的控件来处理事件呢?

    1️⃣、首先判断主窗口是否能接受事件

    2️⃣、判断触摸点是否在自己身上

    3️⃣、子控件数组中从后往前遍历子控件,重复1️⃣、2️⃣两个步骤

    4️⃣、比如有个LHView,那么把这个事件交给这个view,再遍历LHView的子控件,直至没有更合适的为止

    5️⃣、如果没有符合条件的子控件,那么就认为自己最适合处理这个事件

当一个试图接收到事件传递时,就会调用  - hitText方法,这个方法用于判断找出最合适的view,找到最合适的view就会调用响应的处理方法

代码如下

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //1、判断当前的view能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    //2、判断点在不在当前控件
    NSInteger pointCount = self.subviews.count;
    //3、从后往前遍历子控件
    for (NSInteger i = pointCount - 1; i >= 0; i--) {
        
        UIView * childView = self.subviews[i];
        //把当前控件的坐标转换成子控件上的坐标
        CGPoint childPoint = [self convertPoint:point toView:childView];
        UIView * fitView = [childView hitTest:childPoint withEvent:event];
        if (fitView) {
            return fitView;
        }
    }
    //循环结束,没有找到比自己更合适的view
    return self;
}

总结: 

事件的传递与响应(响应者链条)

1、当一个事件发生后,事件会从父控件传给子控件,也就是说有UIApplication -> UIWindow -> UIView -> initial view  的传递,寻找最合适的view的过程,当一个试图接收到事件之后就会调用   -(UIView *)hitTest:(CGpoint) point withEvent:(UIEvent *)event,经过5个步骤来寻找的过程。

2、事件的响应:首先看当前的initial view是否处理这个事件,如果不则会将事件传递给上级试图,如果上级试图仍然无法处理则会继续往上传递,一直传递到控制器 viewController。会判断试图控制器的根试图能否处理此事件,此时还是不能处理则交给UIWindow,如果还不能处理则交给最后的UIApplication,它如果还不能处理的话就丢弃

3、在事件的响应中,如果某个空间实现了touches....的方法,则这个事件将有此控件来接收,如果调用了【super touches。。。】;就会将事件顺着响应者链条网上往上传递,传递给上一个响应者,接着就会调用上一个响应者的touches方法。

====马上结束====

试问:能否做到一个事件多个对象处理

系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到这个目的

试问:事件的传递和响应的区别

事件的传递时从上到下,从父控件到子控件,事件的响应是从下往上,顺着响应者链条向上传递,子控件到父控件

====== 这回真的是马上结束 ======

-(UIResponder *)nextResponder;

它返回接收者下一个响应,没有就返回nil

结束