美文网首页面试宝点
UIControl的触摸事件

UIControl的触摸事件

作者: 程序狗 | 来源:发表于2018-01-18 14:43 被阅读16次

    UIControl是继承于UIView的一个子类,能够响应触摸事件(基本能够响应触摸事件都是继承于此类)
    首先我们来了解一个触摸事件在iOS中是怎么进行的,系统是怎么反馈的


    触摸事件流程.png

    系统响应阶段

    1.手指触碰屏幕,屏幕感应到触碰后,将事件交由IOKit处理。
    2.IOKit将触摸事件封装成一个IOHIDEvent对象,并通过mach port传递给SpringBoad进程。

    mach port 进程端口,各进程之间通过它进行通信。
    SpringBoad.app 是一个系统进程,可以理解为桌面系统,可以统一管理和分发系统接收到的触摸事件。
    

    3.SpringBoard进程因接收到触摸事件,触发了主线程runloop的source1事件源的回调。

    此时SpringBoard会根据当前桌面的状态,判断应该由谁处理此次触摸事件。因为事件发生时,你可能正在桌面上翻页,也可能正在刷微博。若是前者(即前台无APP运行),则触发SpringBoard本身主线程runloop的source0事件源的回调,将事件交由桌面系统去消耗;若是后者(即有app正在前台运行),则将触摸事件通过IPC传递给前台APP进程,接下来的事情便是APP内部对于触摸事件的响应了。

    APP响应阶段

    1. APP进程的mach port接受到SpringBoard进程传递来的触摸事件,主线程的runloop被唤醒,触发了source1回调。
      2.source1回调又触发了一个source0回调,将接收到的IOHIDEvent对象封装成UIEvent对象,此时APP将正式开始对于触摸事件的响应。
      3.source0回调内部将触摸事件添加到UIApplication对象的事件队列中。事件出队后,UIApplication开始一个寻找最佳响应者的过程,这个过程又称hit-testing,细节将在[寻找事件的最佳响应者]一节阐述。另外,此处开始便是与我们平时开发相关的工作了。
      4.寻找到最佳响应者后,接下来的事情便是事件在响应链中的传递及响应了,关于响应链相关的内容详见[事件的响应及在响应链中的传递]一节。事实上,事件除了被响应者消耗,还能被手势识别器或是target-action模式捕捉并消耗掉。其中涉及对触摸事件的响应优先级,详见[事件的三徒弟UIResponder、UIGestureRecognizer、UIControl]一节。
      5.触摸事件历经坎坷后要么被某个响应对象捕获后释放,要么致死也没能找到能够响应的对象,最终释放。至此,这个触摸事件的使命就算终结了。runloop若没有其他事件需要处理,也将重归于眠,等待新的事件到来后唤醒。

    那app是如何做响应的呢?
    每个响应者都是一个UIResponder对象,即所有派生自UIResponder的对象,本身都具备响应事件的能力。因此以下类的实例都是响应者:

    UIView
    UIViewController
    UIApplication
    AppDelegate
    响应者之所以能响应事件,因为其提供了4个处理触摸事件的方法:

    //手指触碰屏幕,触摸开始
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    //手指在屏幕上移动
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    //手指离开屏幕,触摸结束
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    //触摸结束前,某个系统事件中断了触摸,例如电话呼入
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    

    这四个方法稍后再说,我们来看触摸事件是如何传递的
    APP接收到触摸事件后,会被放入当前应用的一个事件队列中,出队后,application首先将事件传递给当前应用最后显示的窗口(UIWindow)询问其能否响应事件。若窗口能响应事件,则传递给子视图询问是否能响应,子视图若能响应则继续询问子视图。子视图询问的顺序是优先询问后添加的子视图,即子视图数组中靠后的视图。
    可以看图


    hit-testing

    视图层级如下:

    A
    ├── B
    │   └── D
    └── C
        ├── E
        └── F
    

    现在假设在E视图所处的屏幕位置触发一个触摸,应用接收到这个触摸事件事件后,先将事件传递给UIWindow,然后自下而上开始在子视图中寻找最佳响应者。事件传递的顺序如下所示:


    hit-testing过程

    ·UIWindow将事件传递给其子视图A
    ·A判断自身能响应该事件,继续将事件传递给C(因为视图C比视图B后添加,因此优先传给C)。
    ·C判断自身能响应事件,继续将事件传递给F(同理F比E后添加)。
    ·F判断自身不能响应事件,C又将事件传递给E。
    ·E判断自身能响应事件,同时E已经没有子视图,因此最终E就是最佳响应者。

    事件的响应

    每个响应者必定都是UIResponder对象,通过4个响应触摸事件的方法来响应事件。每个UIResponder对象默认都已经实现了这4个方法,但是默认不对事件做任何处理,单纯只是将事件沿着响应链传递。若要截获事件进行自定义的响应操作,就要重写相关的方法。例如,通过重写 touchesMoved: withEvent: 方法实现简单的视图拖动。

    //MovedView
    //重写touchesMoved方法(触摸滑动过程中持续调用)
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        //获取触摸对象
        UITouch *touch = [touches anyObject];
        //获取前一个触摸点位置
        CGPoint prePoint = [touch previousLocationInView:self];
        //获取当前触摸点位置
        CGPoint curPoint = [touch locationInView:self];
        //计算偏移量
        CGFloat offsetX = curPoint.x - prePoint.x;
        CGFloat offsetY = curPoint.y - prePoint.y;
        //相对之前的位置偏移视图
        self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
    }
    

    UIControl作为能够响应事件的控件,有其独特的跟踪方式

    - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
    - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
    - (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event;
    - (void)cancelTrackingWithEvent:(nullable UIEvent *)event;
    

    这4个方法和UIResponder的那4个方法几乎吻合,只不过UIControl只能接收单点触控,因此接收的参数是单个UITouch对象。这几个方法的职能也和UIResponder一致,用来跟踪触摸的开始、滑动、结束、取消。不过,UIControl本身也是UIResponder,因此同样有 touches 系列的4个方法。事实上,UIControl的 Tracking 系列方法是在 touch 系列方法内部调用的。比如 beginTrackingWithTouch 是在 touchesBegan 方法内部调用的, 因此它虽然也是UIResponder,但 touches 系列方法的默认实现和UIResponder本类还是有区别的。

    当UIControl跟踪事件的过程中,识别出事件交互符合响应条件,就会触发target-action进行响应。UIControl控件通过 addTarget:action:forControlEvents: 添加事件处理的target和action,当事件发生时,UIControl通知target执行对应的action。说是“通知”其实很笼统,事实上这里有个action传递的过程。当UIControl监听到需要处理的交互事件时,会调用 sendAction:to:forEvent: 将target、action以及event对象发送给全局应用,Application对象再通过 sendAction:to:from:forEvent: 向target发送action。

    image.png

    因此,可以通过重写UIControl的 sendAction:to:forEvent:sendAction:to:from:forEvent: 自定义事件执行的target及action。

    另外,若不指定target,即 addTarget:action:forControlEvents: 时target传空,那么当事件发生时,Application会在响应链上从上往下寻找能响应action的对象。

    相关文章

      网友评论

        本文标题:UIControl的触摸事件

        本文链接:https://www.haomeiwen.com/subject/zibioxtx.html