UIControl介绍以及Target-Action机制

一、UIControl的介绍
UIControl是控件类的基类,它是一个抽象基类,我们不能直接使用UIControl类来实例化控件,它只是为控件子类定义一些通用的接口,并提供一些基础实现,以在事件发生时,预处理这些消息并将它们发送到指定目标对象上。
(1)它的子类有UIButton(按钮)、UIDatePicker(时间/日期选择器)、UIPageControl(分页控件)、UISegmentedControl(分段控件)、UITextFiled(文本框)、UISlider(滑块)、UISwitch(开关),这些控件用来与用户进行交互,响应用户的操作
(2)继承UIControl,自定义的控件,一般基于下面两个原因自定义控件
—对于特定的事件,我们需要观察或修改分发到target对象的行为消息。
—提供自定义的跟踪行为。

二、UIControl的跟踪方法和UIControl属性
(1)UIControl跟踪方法
UIControl为提供了跟踪触摸事件行为的方法如下
如果是想提供自定义的跟踪行为,则可以重写以下几个方法:

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

这四个方法分别对应的时跟踪开始、移动、结束、取消四种状态。看起来是不是很熟悉?这跟UIResponse提供的四个事件跟踪方法是不是挺像的?我们来看看UIResponse的四个方法:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

上面UIControl的跟踪方法和UIResponse的跟踪方法参数基本相同,只不过UIControl的是针对单点触摸,而UIResponse可能是多点触摸。另外,返回值也是大同小异。由于UIControl本身是视图,所以它实际上也继承了UIResponse的这四个方法。如果测试一下,我们会发现在针对控件的触摸事件发生时,这两组方法都会被调用,而且互不干涉。

(2)UIControl属性:
tracking属性:为了判断当前对象是否正在追踪触摸操作。该值如果为YES,则表明正在追踪。这对于我们是更加方便了,不需要自己再去额外定义一个变量来做处理。
touchInside只读属性:使用touchInside属性可以判断当前触摸点是否在控件区域类 ;不过实测的结果是,在控件区域周边一定范围内,该值还是会被标记为YES,即用于判定touchInside为YES的区域会比控件区域要大。在测试中,我们可以发现当我们的触摸点沿着屏幕移出控件区域名,还是会继续追踪触摸操作,cancelTrackingWithEvent:消息并未被发送

三、Target-Action机制
Target-action是一种设计模式,直译过来就是”目标-行为”。当我们通过代码为一个按钮添加一个点击事件时,通常是如下处理:

[button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];
   当按钮点击事件发生时,事件会被发送到控件对象中,然后再由这个控件对象去触发target(此处是self)对象上的action行为(tapButton:方法),来最终处理事件。因此,Target-Action机制由两部分组成:即目标对象和行为Selector。目标对象指定最终处理事件的对象,而行为Selector则是处理事件的方法。

这里写图片描述

分析:实际上,对于一个给定的事件,UIControl会调用sendAction:to:forEvent:来将行为消息转发到UIApplication对象,再由UIApplication对象调用其sendAction:to:fromSender:forEvent:方法来将消息分发到指定的target上,而如果我们没有指定target,则会将事件分发到响应链上第一个想处理消息的对象上。而如果子类想监控或修改这种行为的话,则可以重写这个方法。下面的例子中我们会重写sendAction:to:forEvent方法,让自定义的UUIControl类自己处理按钮事件

sendAction:to:forEvent:实际上也被UIControl的另一个方法所调用,即sendActionsForControlEvents:。这个方法的作用是发送与指定类型相关的所有行为消息。我们可以在任意位置(包括控件内部和外部)调用控件的这个方法来发送参数controlEvents指定的消息。

四、实例:一个带Label的图片控件
我们将实现一个带Label的图片控件。修改分发到target对象的行为,将事件传
递到对象本身来处理,即调用handleAction:方法;而不是UIVIewController的的tapImageControl:方法。
这里写图片描述

这个控件很简单,以图片为背景,然后在下方显示一个Label。
ImageControl.h

@interface ImageControl : UIControl
- (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title image:(UIImage *)image;
@end

ImageControl.m

//重写sendAction:to:forEvent方法,修改分发到target对象的行为消息。让ImageControl自己处理按钮事件,即点击按钮触发handleAction,而不是tapImageControl:方法

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
  // 将事件传递到对象本身来处理
    [super sendAction:@selector(handleAction:) to:self forEvent:event];
}

- (void)handleAction:(id)sender {
    NSLog(@"handle Action");
}

ViewController.m

-

 (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];

    ImageControl *control = [[ImageControl alloc] initWithFrame:(CGRect){50.0f, 100.0f, 200.0f, 300.0f} title:@"This is a demo" image:[UIImage imageNamed:@"demo"]];

    [control addTarget:self action:@selector(tapImageControl:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)tapImageControl:(id)sender {
    NSLog(@"sender = %@", sender);
}

前面介绍了sendAction:to:forEvent:实际上也被UIControl的另一个方法所调用,即sendActionsForControlEvents:。这个方法的作用是发送与指定类型相关的所有行为消息。我们可以在任意位置(包括控件内部和外部)调用控件的这个方法来发送参数controlEvents指定的消息。在我们的示例中,在ViewController.m中作了如下测试:

- (void)viewDidLoad {
    // ...
    [control addTarget:self action:@selector(tapImageControl:) forControlEvents:UIControlEventTouchUpInside];

    [control sendActionsForControlEvents:UIControlEventTouchUpInside];
}

可以看到在未点击控件的情况下,触发了UIControlEventTouchUpInside事件,并打印了handle Action日志。

五、Target-Action的管理
(1)UIControl提供的管理target-action的方法
为一个控件对象添加、删除Target-Action的操作使用的是以下两个方法:

- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents

- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents

allTargets方法:获取控件对象所有相关的target对象,该方法返回一个集合。集合中可能包含NSNull对象,表示至少有一个nil目标对象。

actionsForTarget:forControlEvent:方法:想获取某个target对象及事件相关的所有action,则可以调用。

(2)UIControl是如何去管理Target-Action
实际上,我们在程序某个合适的位置打个断点来观察UIControl的内部结构,可以看到这样的结果:
这里写图片描述
因此,UIControl内部实际上是有一个可变数组(_targetActions)来保存Target-Action,数组中的每个元素是一个UIControlTargetAction对象。UIControlTargetAction类是一个私有类,我们可以在iOS-Runtime-Header中找到它的头文件:

@interface UIControlTargetAction : NSObject {
    SEL _action;
    BOOL _cancelled;
    unsigned int _eventMask;
    id _target;
}

@property (nonatomic) BOOL cancelled;

- (void).cxx_destruct;
- (BOOL)cancelled;
- (void)setCancelled:(BOOL)arg1;
- 
@end

可以看到UIControlTargetAction对象维护了一个Target-Action所必须的三要素,即target,action及对应的事件eventMask。
如果仔细想想,会发现一个有意思的问题。我们来看看实例中ViewController(target)与ImageControl实例(control)的引用关系,如下图所示:
这里写图片描述
嗯,循环引用。
既然这样,就必须想办法打破这种循环引用。那么在这5个环节中,哪个地方最适合做这件事呢?仔细思考一样,1、2、4肯定是不行的,3也不太合适,那就只有5了。在上面的UIControlTargetAction头文件中,并没有办法看出_target是以weak方式声明的,那有证据么?
我们在工程中打个Symbolic断点,如下所示:
这里写图片描述
运行程序,程序会进入[UIControl addTarget:action:forControlEvents:]方法的汇编代码页,在这里,我们可以找到一些蛛丝马迹。如下图所示:
这里写图片描述
可以看到,对于_target成员变量,在UIControlTargetAction的初始化方法中调用了objc_storeWeak,即这个成员变量对外部传进来的target对象是以weak的方式引用的。
其实在UIControl的文档中,addTarget:action:forControlEvents:方法的说明还有这么一句:
When you call this method, target is not retained.
另外,如果我们以同一组target-action和event多次调用addTarget:action:forControlEvents:方法,在_targetActions中并不会重复添加UIControlTargetAction对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的修养

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值