(七)UIControl

本文系转载,原文地址为iOS触摸事件全家桶

UIControl是系统提供的能够以target-action模式处理触摸事件的控件,iOS中UIButton、UISegmentedControl、UISwitch等控件都是UIControl的子类。当UIControl跟踪到触摸事件时,会向其上添加的target发送事件以执行action。值得注意的是,UIConotrol是UIView的子类,因此本身也具备UIResponder应有的身份。

关于UIControl,此处介绍两点:

  1. target-action执行时机及过程
  2. 触摸事件优先级

target-action

  • target:处理交互事件的对象
  • action:处理交互事件的方式

UIControl作为能够响应事件的控件,必然也需要待事件交互符合条件时才去响应,因此也会跟踪事件发生的过程。不同于UIResponder以及UIGestureRecognizer通过 touches 系列方法跟踪,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

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

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

If you specify nil for the target object, the control searches the responder chain for an object that defines the specified action method.

触摸事件优先级

当原本关系已经错综复杂的UIGestureRecognizer和UIResponder之间又冒出一个UIControl,又会摩擦出什么样的火花呢?

In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer.This applies only to gesture recognition that overlaps the default action for a control, which includes:

  • A single finger single tap on a UIButton, UISwitch, UIStepper, UISegmentedControl, and UIPageControl.
  • A single finger swipe on the knob of a UISlider, in a direction parallel to the slider.
  • A single finger pan gesture on the knob of a UISwitch, in a direction parallel to the switch.

简单理解:UIControl会阻止父视图上的手势识别器行为,也就是UIControl处理事件的优先级比UIGestureRecognizer高,但前提是相比于父视图上的手势识别器。

image
  • 预置场景:在BlueView上添加一个button,同时给button添加一个target-action事件。

    • 示例一:在BlueView上添加点击手势识别器
    • 示例二:在button上添加手势识别器
  • 操作方式:单击button

  • 测试结果:示例一中,button的target-action响应了单击事件;示例二中,BlueView上的手势识别器响应了事件。过程日志打印如下:

    //示例一
    -[CLTapGestureRecognizer touchesBegan:withEvent:]
    -[CLButton touchesBegan:withEvent:]
    -[CLButton beginTrackingWithTouch:withEvent:]
    -[CLTapGestureRecognizer touchesEnded:withEvent:] after called state = 5
    -[CLButton touchesEnded:withEvent:]
    -[CLButton endTrackingWithTouch:withEvent:]
    按钮点击
    
    
    //示例二
    -[CLTapGestureRecognizer touchesBegan:withEvent:]
    -[CLButton touchesBegan:withEvent:]
    -[CLButton beginTrackingWithTouch:withEvent:]
    -[CLTapGestureRecognizer touchesEnded:withEvent:] after called state = 3
    手势触发
    -[CLButton touchesCancelled:withEvent:]
    -[CLButton cancelTrackingWithEvent:]
    
    
  • 原因分析:点击button后,事件先传递给手势识别器,再传递给作为hit-tested view存在的button(UIControl本身也是UIResponder,这一过程和普通事件响应者无异)。示例一中,由于button阻止了父视图BlueView中的手势识别器的识别,导致手势识别器识别失败(状态为failed 枚举值为5),button完全接手了事件的响应权,事件最终由button响应;示例二中,button未阻止其本身绑定的手势识别器的识别,因此手势识别器先识别手势并识别成功(状态为ended 枚举值为3),而后通知Application取消响应链对事件的响应,因为 touchesCancelled 被调用,同时 cancelTrackingWithEvent 跟着调用,因此button的target-action得不到执行。

  • 其他:经测试,若示例一中的手势识别器设置 cancelsTouchesInView 为NO,手势识别器和button都能响应事件。也就是说这种情况下,button不会阻止父视图中手势识别器的识别。

  • 结论:UIControl比其父视图上的手势识别器具有更高的事件响应优先级。

TODO:
上述过程中,手势识别器在执行touchesEnded时是根据什么将状态置为ended还是failed的?即根据什么判断应当识别成功还是识别失败?

纠正 2017.11.17

以上所述UIControl的响应优先级比手势识别器高的说法不准确,准确地说只适用于系统提供的有默认action操作的UIControl,例如UIbutton、UISwitch等的单击,而对于自定义的UIControl,经验证,响应优先级比手势识别器低。读者可自行验证,感谢 @闫仕伟 同学的纠正。

下一篇 (八)解释栗子

你可能感兴趣的:((七)UIControl)