iOS事件机制(点击、手势、UIControl)

一.触摸、事件、响应者、手势、UIControl

1. UITouch

  • 一个手指对应一个UITouch对象,多个手指同时触摸屏幕,对应多个UITouch对象。

  • 系统会根据同一位置极短时间的点击次数来判断UITouch的点击次数,比如第一次点击生成了tapCount为1的一个UITouch,极短时间内相同位置的点击,系统会丢弃第一次生成的UITouch对象,重新生成一个UITouch对象,并且该tapCount为2(有说法是说只是更新第一次生成的UITouch的tapCount为2,本人测试的是Xcode12, iphone12, iOS 14.1模拟器环境,是重新生成的一个UITouch)。

  • 每个UITouch对象记录了触摸的一些信息,包括触摸时间、位置、阶段、所处的视图、窗口等信息。

  • UITouch有多个状态,每个状态改变的时候会回调UIResponder的四个处理UITouch的响应方法。

touchesMethod.jpg

注意:对于一个手指的触摸,是UITouch每次状态改变的时候都会回调UIResponder相对应的处理方法。对于多个手指的触摸,也许多个UITouch状态的改变一起回调UIResponder的处理方法,也许每个UITouch状态的改变都会回调UIResponder的处理方法,例如,两个点击,可能只有一个touchesBegan的回调,两个touchesEnded的回调.同时,多个UIControl状态改变只有一次touchesBegan等方法回调的参数touches里touch的个数我测试的时候只有一个,不要以为所有状态改变的UITouch只有一次回调时都会放到touches参数里。关于多点触摸的处理个人不建议在UITouch的响应机制里去做处理,里面具体原理并不明朗,实际开发中的借鉴也不多,涉及多点触摸使用手势更好。

// 触摸的各个阶段状态 
// 例如当手指移动时,会更新phase属性到UITouchPhaseMoved;
// 手指离屏后,更新到UITouchPhaseEnded
typedef NS_ENUM(NSInteger, UITouchPhase) {
    UITouchPhaseBegan,             // 开始接触屏幕
    UITouchPhaseMoved,             // 在屏幕上移动
    UITouchPhaseStationary,        // whenever a finger is touching the surface but hasn't moved since the previous event.自从上一次事件后手指在屏幕上没有移动
    UITouchPhaseEnded,             // 手指离开屏幕
    UITouchPhaseCancelled,         // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face) 手指虽然没有离开屏幕,但是需要停止跟踪手指
};

2.UIEvent

  • 触摸生成触摸事件,一个触摸事件对应一个UIEvent对象。UIEvent的type属性标识了事件的类型,事件有如下几种类型:我们这里说的事件指的是触摸事件UIEventTypeTouches。
typedef NS_ENUM(NSInteger, UIEventType) {
    UIEventTypeTouches,
    UIEventTypeMotion,
    UIEventTypeRemoteControl,
    UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
};
  • UIEvent对象中包含了触发该事件的所有触摸,可以通过触摸对象allTouches属性获取。比如两次触摸连续两次点击.

为了跟踪UITouch的处理过程,我们自定义UIView覆盖重写了touch的回调处理方法,如下:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(CACurrentMediaTime()) // 当前时间
        print(#function)  // 打印函数名字
        print(touches)  // touches参数
        print(event!)  // event参数
        super.touchesBegan(touches, with: event)  // 系统默认实现
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(CACurrentMediaTime())
        print(#function)
        print(touches)
        print(event!)
        super.touchesEnded(touches, with: event)
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(CACurrentMediaTime())
        print(#function)
        print(touches)
        print(event!)
        super.touchesCancelled(touches, with: event)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(CACurrentMediaTime())
        print(#function)
        print(touches)
        print(event!)
        super.touchesMoved(touches, with: event)
    }

在自定义UIView上两根手指触摸连续点两次,,回调过程如下:

226116.34713520834
touchesBegan(_:with:)
[<UITouch: 0x1054046c0> phase: Began tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226116 touches: {(
    <UITouch: 0x105501090> phase: Began tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {125, 745.5} previous location in window: {125, 745.5} location in view: {125, 745.5} previous location in view: {125, 745.5},
    <UITouch: 0x1054046c0> phase: Began tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}
)}
226116.39147158334
touchesEnded(_:with:)
[<UITouch: 0x1054046c0> phase: Ended tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226116 touches: {(
    <UITouch: 0x105501090> phase: Ended tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {125, 745.5} previous location in window: {125, 745.5} location in view: {125, 745.5} previous location in view: {125, 745.5},
    <UITouch: 0x1054046c0> phase: Ended tap count: 1 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {261, 713.5} previous location in window: {261, 713.5} location in view: {261, 513.5} previous location in view: {261, 513.5}
)}
226116.519107625
touchesBegan(_:with:)
[<UITouch: 0x1082029e0> phase: Began tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {265, 722} previous location in window: {265, 722} location in view: {265, 522} previous location in view: {265, 522}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
    <UITouch: 0x1082029e0> phase: Began tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {265, 722} previous location in window: {265, 722} location in view: {265, 522} previous location in view: {265, 522}
)}
226116.5276584167
touchesMoved(_:with:)
[<UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {252.5, 717.5} previous location in window: {265, 722} location in view: {252.5, 517.5} previous location in view: {265, 522}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
    <UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {252.5, 717.5} previous location in window: {265, 722} location in view: {252.5, 517.5} previous location in view: {265, 522},
    <UITouch: 0x1054046c0> phase: Began tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {115, 753.5} previous location in window: {115, 753.5} location in view: {115, 753.5} previous location in view: {115, 753.5}
)}
226116.53451820835
touchesMoved(_:with:)
[<UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {249, 717.5} previous location in window: {252.5, 717.5} location in view: {249, 517.5} previous location in view: {252.5, 517.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
    <UITouch: 0x1082029e0> phase: Moved tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {249, 717.5} previous location in window: {252.5, 717.5} location in view: {249, 517.5} previous location in view: {252.5, 517.5},
    <UITouch: 0x1054046c0> phase: Stationary tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {115, 753.5} previous location in window: {115, 753.5} location in view: {115, 753.5} previous location in view: {115, 753.5}
)}
226116.57586620835
touchesEnded(_:with:)
[<UITouch: 0x1082029e0> phase: Ended tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {247.5, 718.5} previous location in window: {249, 717.5} location in view: {247.5, 518.5} previous location in view: {249, 517.5}]
<UITouchesEvent: 0x2829b2260> timestamp: 226117 touches: {(
    <UITouch: 0x1082029e0> phase: Ended tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: <TouchEventProj.TestView: 0x10560c8c0; frame = (0 200; 414 696); autoresize = RM+BM; tag = 2; layer = <CALayer: 0x2818a2680>> location in window: {247.5, 718.5} previous location in window: {249, 717.5} location in view: {247.5, 518.5} previous location in view: {249, 517.5},
    <UITouch: 0x1054046c0> phase: Ended tap count: 2 force: 0.000 window: <UIWindow: 0x105608dc0; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x28163c8a0>; layer = <UIWindowLayer: 0x2818a3400>> view: (null) location in window: {115, 753.5} previous location in window: {115, 753.5} location in view: {115, 753.5} previous location in view: {115, 753.5}
)}

3.UIResponder

UIResponder是iOS中用于处理用户事件的API,可以处理触摸事件、按压事件(3D touch)、远程控制事件、硬件运动事件。可以通过touchesBegan、pressesBegan、motionBegan、remoteControlReceivedWithEvent等方法,获取到对应的回调消息。UIResponder不只用来接收事件,还可以处理和传递对应的事件,如果当前响应者不能处理,则转发给其他合适的响应者处理。
应用程序通过响应者来接收和处理事件,响应者可以是继承自UIResponder的任何子类,例如UIView、UIViewController、UIApplication等。当事件来到时,系统会将事件传递给合适的响应者,并且将其成为第一响应者。
第一响应者未处理的事件,将会在响应者链中进行传递,传递规则由UIResponder的nextResponder决定,可以通过重写该属性来决定传递规则。当一个事件到来时,第一响应者没有接收消息,则顺着响应者链向后传递。

UIReponderChain.png
nextResponder.png

总结:
1)view的nextResponder,是viewController的rootView,则是viewController,否则为superView
2)viewController的nextResponder, 是window的rootViewController,则是window,否则为rootView的superView

  1. window的nextResponder为UIApplication对象
    4)UIApplication:若当前应用的app delegate是一个UIResponder对象,且不是UIView、UIViewController或app本身,则UIApplication的nextResponder为app delegate

4.UIGestureRecognizer

Gesture Recognizer 是对底层事件处理的封装,是为了让使用者能够更简单处理事件。
手势分为离散型手势(discrete gestures)和持续型手势(continuous gesture)。

  • 离散手势识别(Discrete Gesture Recognizer)
    UITapGestureRecognizer
    UISwipeGestureRecognizer
    UILongPressGestureRecognizer

  • 连续手势识别(Continuous Gesture Recognizer)
    UIPinchGestureRecognizer
    UIPanGestureRecognizer
    UIRotationGestureRecognizer

手势响应过程:


target-action.jpeg

手势状态:


gestureState.png

离散手势状态改变过程:
Possible ----> Failed
Possible ----> Recognized
连续手势状态改变过程:
Possible ----> Began ----> [Changed] ----> Cancelled
Possible ----> Began ----> [Changed] ----> Ended
changed为可选状态。

5.UIControl

UIControl是系统提供的能够以target-action模式处理触摸事件的控件,iOS中UIButton、UISegmentedControl、UISwitch等控件都是UIControl的子类。

值得注意的是,UIConotrol是UIView的子类,因此本身也具备UIResponder应有的身份。

UIControl作为控件类的基类,它是一个抽象基类,我们不能直接使用UIControl类来实例化控件,它只是为控件子类定义一些通用的接口,并提供一些基础实现,以在事件发生时,预处理这些消息并将它们发送到指定目标对象上。

Action的形式
@IBAction func doSomething()
@IBAction func doSomething(sender: UIButton)
@IBAction func doSomething(sender: UIButton, forEvent event: UIEvent)

识别重要方法:

  • (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;

识别行为成功后发送消息:

  • (void)sendActionsForControlEvents:(UIControlEvents)controlEvents;

UIControl的触发过程:


endTrackingWithTouch.jpg

四个重要识别方法是在touchesBegan、touchesMoved、touchedEnded、touchesCancelled里回调的。

action调用.jpg

推测是:endTrackingWithTouch调用后识别了行为,做标记,返回到touchesEnded后,判断本UIControl是否易识别行为,调用行为回调。

二.事件传递机制:

 手势、UITouch、UIControl的处理机制都是不一样的,特别是UIControl的处理机制仍然像个迷,只能根据想象进行推测。手势、UITouch、UIControl的处理机制第一步是相同的都是找到响应者,第二步处理将开始不同。

1.找到第一个响应者:

App接收到触摸事件后,会被放入当前应用程序的UIApplication维护的事件队列中.
由于事件一次只有一个,但是能够响应的事件的响应者众多,所以这就存在一个寻找第一响应者的过程。
调用方法,获取到被点击的视图,也就是第一响应者。

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

hitTest:withEvent:方法内部会通过调用pointInside:这个方法,来判断点击区域是否在视图上,是则返回YES,不是则返回NO。

2.没有UIGestureRecognizer和UIControl的响应

经过Hit-Testing的过程后,UIApplication已经知道了第一响应者是谁,接下来要做的事情就是:

1)将事件传递给第一响应者
2)响应者处理或者将事件沿着响应链传递

3.有UIGestureRecognizer的响应

自定义的view的touchesBegan、touchesMoved、touchesEnded、touchedCancelled四个方法重写,记录打印过程,该view上添加tapGestureRecognized手势,该tapGestureRecognized也覆写了这四个方法。

//
//  TestView.swift
//  TouchEventProj
//
//  Created by littledou on 2020/11/12.
//

import UIKit

class TestView : UIView { // 自定义UIView方法,覆写响应方法
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesBegan(touches, with: event)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesEnded(touches, with: event)
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesCancelled(touches, with: event)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesMoved(touches, with: event)
    }
}

class TestGestureRecognizer : UITapGestureRecognizer { // 自定义UITapGestureRecognizer方法,覆写识别手势的方法
    var tag:NSInteger?
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesBegan(touches, with: event)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesEnded(touches, with: event)
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesCancelled(touches, with: event)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        print(NSStringFromClass(self.classForCoder))
        print(#function)
        super.touchesMoved(touches, with: event)
    }
}

点击view调用打印过程输出:

TouchEventProj.TestGestureRecognizer
touchesBegan(_:with:)
TouchEventProj.TestView
touchesBegan(_:with:)
TouchEventProj.TestGestureRecognizer
touchesEnded(_:with:)
tapViewClicked(gestureRecognize:) // 手势识别成功,调用手势响应方法
TouchEventProj.TestView
touchesCancelled(_:with:)

调用栈:

调用栈.png

结合上面的输出和调用栈,我们可能并不能明确的看出有手势的时候点击的过程,不过如果你自己调试,是能得出如下结论的:

有手势的时候点击的大致调用过程:
1)hit-test找到第一响应者,作为UITouch是能记住第一响应者的。
2)更新响应者和响应者的上级视图上所有的手势状态
3)同时第一响应者处理UITouch的状态回调
4)响应者和响应者的上级视图上所有的手势状态中有识别成功的向UITouch发送cancelled消息

默认调用路径.png

UIGestureRecognizer和UITouch的关系可以由UIGestureRecognizer的三个属性影响:cancelsTouchesInView、delaysTouchesBegan、delaysTouchesEnded。

cancelsTouchesInView:默认为YES。表示当手势识别器成功识别了手势之后,会通知Application取消响应链对事件的响应,并不再传递事件给第一响应者。若设置成NO,表示手势识别成功后不取消响应链对事件的响应,事件依旧会传递给第一响应者。

delaysTouchesBegan:默认为NO。默认情况下手势识别器在识别手势期间,当触摸状态发生改变时,Application都会将事件传递给手势识别器和第一响应者;若设置成YES,则表示手势识别器在识别手势期间,截断事件,即不会将事件发送给第一响应者。

delaysTouchesEnded:默认为YES。当手势识别失败时,若此时触摸已经结束,会延迟一小段时间(0.15s)再调用响应者的touchesEnded:withEvent:;若设置成NO,则在手势识别失败时会立即通知Application发送状态为end的touch事件给第一响应者以调用 touchesEnded:withEvent:结束事件响应。这个属性的效果并不是很明显,其实际使用中的作用也并不大。

3.有UIControl的响应

这里主要是针对系统定义对UIControl对子类,因为UIControl对响应原理并不是太明朗,只能根据现象进行推测。
UIControl的响应并不会影响UIResponder对点击对响应处理,处理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;  

本身就是在UIResponder的UITouchesBegan、UITouchesMoved、UITouchedEnded、UITouchesCancel四个回调中调用的。
UIControl的响应处理并不会影响UIResponder的响应链的处理,但是UIControl会影响另一个UIControl,子视图的UIControl具有优先级。

4.有UIGestureRecognizer和UIControl的响应

UIGestureRecognizer和UIControl并没有决定的优先级。
从iOS6开始在控件的父视图上面添加相应的手势,控件就会控制阻止手势行为,比如:
tap 手势在 UIButton,UISwitch,UIStepper,UISegmentControl,UIPageControl;
swipe 手势在 UISlider;
pan 手势在 UISwitch;
其他可能是手势优于控件的行为。

三.总结

UIResponder有touchesBegan等四个方法,默认向superview传递。
所有需要自定义点击处理逻辑的UIResponder子类要覆盖这四个方法。
点击事件由四个方法处理。
UIButton的处理也是需要经过这四个方法。
UIGestureRecognizer也有touchesBegan等四个方法。
手势不在响应链里,但是也会观察它的view和subView的点击。

UIGestureRecognizer会影响UIResponder的四个响应点击的方法。
默认点击事件响应关键步骤说明:
1)用户手指点击屏幕,经过系统传递到UIApplication, UIApplication通过hitTest:方法找到对应UITouch发生的第一响应者view
2)UIApplication更新手势状态,从第一响应者上的手势到其视图层上所有先辈视图上的手势都会接收这个UITouch来更新手势状态
3)UIApplication将UITouch交给找到的第一响应着view处理
4)UIApplication更新手势状态,识别成功后,会向UITouch的第一响应者发送cancel方法

加上UIControl会让过程变得复杂,关于UIControl的原理,不清楚,也不敢妄下结论,依据网上和实际测试大致推断:
1)它不会影响UITouch本身的响应流程,但是会影响其他UIControl和UIGestureRecognizer的响应
2)自定义的UIControl是和UITouch本身的响应过程是一样的
3)系统定义的UIControl和UIGestureRecognizer同一个优先级,谁先识别出来,另一个就out了,但是UIControl和UIGestureRecognizer有一点不同,它并不会cancel UITouch的流程。

关于UITouch、UIGestureRecognizer、UIControl之间影响说明:
1)UITouch和UIGestureRecognizer:UIGestureRecognizer优先级高于UITouch,由UIGestureRecognizer的三个参数cancelsTouchesInView、delaysTouchesBegan、delaysTouchesEnded决定对UITouch的影响,默认情况下,UIGestureRecognizer识别成功后,会向UITouch发送cancel
避免:
1)尽量不要覆盖重写UIResponder的touchesBegin、touchesMoved、touchesCancelled、touchesEnded这四个方法,如果需要覆盖重写,逻辑应该尽量简单,不宜做复杂的处理,
2)不要自定义UIControl,直接使用系统定义的UIControl
3)UIControl上不要添加UIControl子视图
4)不要依赖UIGestureRecognizer的delayTouchBegin和delayTouchEnded
5)不要自定义UIGestureRecognizer

参考文章:
1)iOS 事件(UITouch、UIControl、UIGestureRecognizer)传递机制
2)Touch Event Handing 教學 — part 1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容