iOS - Target-Action机制创建自己的UI控件需要了解的知识
我们在开发应用的时候,经常会用到各种各样的控件,诸如按钮(UIButton
)、滑块(UISlider
)、分页控件(UIPageControl
)等。这些控件用来与用户进行交互,响应用户的操作。我们查看这些类的继承体系,可以看到它们都是继承于UIControl
类。UIControl
是控件类的基类,它是一个抽象基类,我们不能直接使用UIControl
类来实例化控件,它只是为控件子类定义一些通用的接口,并提供一些基础实现,以在事件发生时,预处理这些消息并将它们发送到指定目标对象上。
本文将通过一个自定义的UIControl
子类来看看UIControl
的基本使用方法。不过在开始之前,让我们先来了解一下Target-Action
机制。
Target-Action机制
Target-action
是一种设计模式,直译过来就是”目标-行为”。当我们通过代码为一个按钮添加一个点击事件时,通常是如下处理:
1
|
[button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside]; |
也就是说,当按钮的点击事件发生时,会将消息发送到target
(此处即为self对象),并由target
对象的tapButton:
方法来处理相应的事件。其基本过程可以用下图来描述:
注:图片来源于官方文档Cocoa Application Competencies for iOS – Target Action
即当事件发生时,事件会被发送到控件对象中,然后再由这个控件对象去触发target
对象上的action
行为,来最终处理事件。因此,Target-Action
机制由两部分组成:即目标对象和行为Selector
。目标对象指定最终处理事件的对象,而行为Selector
则是处理事件的方法。
有关Target-Action
机制的具体描述,大家可以参考Cocoa Application Competencies for iOS – Target Action。我们将会在下面讨论一些Target-action
更深入的东西。
实例:一个带Label的图片控件
回到我们的正题来,我们将实现一个带Label的图片控件。通常情况下,我们会基于以下两个原因来实现一个自定义的控件:
对于特定的事件,我们需要观察或修改分发到
target
对象的行为消息。提供自定义的跟踪行为。
本例将会简单地结合这两者。先来看看效果:
这个控件很简单,以图片为背景,然后在下方显示一个Label。
先创建UIControl
的一个子类,我们需要传入一个字符串和一个UIImage对象:
1
2
3
4
5
|
@interface ImageControl : UIControl - (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title image:(UIImage *)image; @end |
基础的布局我们在此不讨论。我们先来看看UIControl
为我们提供了哪些自定义跟踪行为的方法。
跟踪触摸事件
如果是想提供自定义的跟踪行为,则可以重写以下几个方法:
1
2
3
4
|
- (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
的四个方法:
1
2
3
4
|
- (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
定义了一个tracking
属性。该值如果为YES,则表明正在追踪。这对于我们是更加方便了,不需要自己再去额外定义一个变量来做处理。
在测试中,我们可以发现当我们的触摸点沿着屏幕移出控件区域名,还是会继续追踪触摸操作,cancelTrackingWithEvent:
消息并未被发送。为了判断当前触摸点是否在控件区域类,可以使用touchInside
属性,这是个只读属性。不过实测的结果是,在控件区域周边一定范围内,该值还是会被标记为YES,即用于判定touchInside
为YES的区域会比控件区域要大。
观察或修改分发到target对象的行为消息
对于一个给定的事件,UIControl
会调用sendAction:to:forEvent:
来将行为消息转发到UIApplication
对象,再由UIApplication
对象调用其sendAction:to:fromSender:forEvent:
方法来将消息分发到指定的target
上,而如果我们没有指定target
,则会将事件分发到响应链上第一个想处理消息的对象上。而如果子类想监控或修改这种行为的话,则可以重写这个方法。
在我们的实例中,做了个小小的处理,将外部添加的Target-Action
放在控件内部来处理事件,因此,我们的代码实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// ImageControl.m - (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:
方法,所以最后处理事件的Selector
是ImageControl
的handleAction:
方法,而不是ViewController的tapImageControl:
方法。
另外,sendAction:to:forEvent:
实际上也被UIControl
的另一个方法所调用,即sendActionsForControlEvents:
。这个方法的作用是发送与指定类型相关的所有行为消息。我们可以在任意位置(包括控件内部和外部)调用控件的这个方法来发送参数controlEvents
指定的消息。在我们的示例中,在ViewController.m中作了如下测试:
1
2
3
4
5
6
|
- (void)viewDidLoad { // ... [control addTarget:self action:@selector(tapImageControl:) forControlEvents:UIControlEventTouchUpInside]; [control sendActionsForControlEvents:UIControlEventTouchUpInside]; } |
可以看到在未点击控件的情况下,触发了UIControlEventTouchUpInside
事件,并打印了handle Action
日志。
Target-Action的管理
为一个控件对象添加、删除Target-Action
的操作我们都已经很熟悉了,主要使用的是以下两个方法:
1
2
3
4
|
// 添加 - (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents - (void)removeTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents |
如果想获取控件对象所有相关的target对象,则可以调用allTargets
方法,该方法返回一个集合。集合中可能包含NSNull
对象,表示至少有一个nil目标对象。
而如果想获取某个target对象及事件相关的所有action,则可以调用actionsForTarget:forControlEvent:
方法。
不过,这些都是UIControl
开放出来的接口。我们还是想要探究一下,UIControl
是如何去管理Target-Action
的呢?
实际上,我们在程序某个合适的位置打个断点来观察UIControl
的内部结构,可以看到这样的结果:
因此,UIControl
内部实际上是有一个可变数组(_targetActions
)来保存Target-Action
,数组中的每个元素是一个UIControlTargetAction
对象。UIControlTargetAction
类是一个私有类,我们可以在iOS-Runtime-Header中找到它的头文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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
对象。
小结
控件是我们在开发中常用的视图工具,能很好的表达用户的意图。我们可以使用UIKit提供的控件,也可以自定义控件。当然,UIControl
除了上述的一些方法,还有一些属性和方法,以及一些常量,大家可以参考文档。
iOS - Target-Action机制创建自己的UI控件需要了解的知识的更多相关文章
- ios学习笔记第三天之UI控件
- UI控件初始化问题:initWithFrame和initWithCoder、aweakFromNib的执行
在iOS学习和程序开发过程中,我们经常会遇到一些自定义UI控件或控制器在初始化时出现问题,尤其在大家刚开始接触时,几种初始化方法的作用以及调用的时机往往容易混淆,这也跟我们对iOS程序设计中,类的创建 ...
- 【Unity】8.1 Unity内置的UI控件
分类:Unity.C#.VS2015 创建日期:2016-04-27 一.简介 Unity 5.x内置了-套完整的GUI系统,提供了从布局.控件到皮肤的-整套GUI解决方案,因此可直接利用它做出各种风 ...
- 【iOS 开发】基本 UI 控件详解 (UIButton | UITextField | UITextView | UISwitch)
博客地址 : http://blog.csdn.net/shulianghan/article/details/50051499 ; 一. UI 控件简介 1. UI 控件分类 UI 控件分类 : 活 ...
- 【IOS 开发】基本 UI 控件详解 (UIDatePicker | UIPickerView | UIStepper | UIWebView | UIToolBar )
转载注明出处 : http://blog.csdn.net/shulianghan/article/details/50348982 一. 日期选择器 (UIDatePicker) UIDatePic ...
- 线程池内的异步线程创建UI控件,造成UI线程卡死无响应的问题分析
winform应用在使用一段时间后,切换到其他系统或者打开word.excel文档,再切换回winform应用时,系统有时出现不响应的现象.有时在锁屏后恢复桌面及应用时也发生此问题. 经微软支持确认, ...
- 用swift创建各种UI控件【iSwifting社区】
为了方便大家学习,www.iSwifting.com社区为大家准备了创建各种UI控件的代码.開始看着语法可能有些别扭,当用习惯了,就认为还是非常不错的. 社区还添加了问答专区.有问题的朋友.虽然问.大 ...
- iOS基础UI控件介绍-Swift版
iOS基础UI控件总结 iOS基础控件包括以下几类: 1.继承自NSObject:(暂列为控件) UIColor //颜色 UIImage //图像 2.继承自UIView: 只能相应手势UIGest ...
- IOS学习资源收集--开发UI控件相关
收集的一些本人了解过的iOS开发UI控件相关的代码资源(本文持续补充更新) 内容大纲: 1.本人在github上也上传了我分装好的一些可重复利用的UI控件 2.计时相关的自定义UILabel控件 正文 ...
随机推荐
- Kafka(四) —— KafkaProducer源码阅读
一.doSend()方法 Kafka中的每一条消息都对应一个ProducerRecord对象. public class ProducerRecord<K, V> { private fi ...
- svg入门详解
一.svg是什么? SVG 意为可缩放矢量图形(Scalable Vector Graphics). SVG 是使用 XML 来描述二维图形和绘图程序的语言. SVG 图像在放大或改变尺寸的情况下其图 ...
- Unity3D获取Android平台的电量
刚开始的时候以为这个应该不简单.我也开始百度,寻找获取手机的电量的方法.大概有俩种方式:一种是直接访问一个文件,意思是说Android手机的电量等信息保存到了这个文件中.但是我试验的时候没有访问出来, ...
- ‘Skimming-Perusal’ Tracking: A Framework for Real-Time and Robust Long-term Tracking
‘Skimming-Perusal’ Tracking: A Framework for Real-Time and Robust Long-term Tracking 2019-09-05 21:1 ...
- 【winform】主窗体多线程给子窗体传值
1.主窗体多线程给子窗体传值 解决方案:主要使用委托,因为会出现跨线程错误 主窗体 public FormMain() { InitializeComponent(); //background th ...
- SQLite添加新的字段
通过alter添加新的字段SQL语句 "ALTER TABLE 'DiHKChatMessage' ADD 'phoneNum' varchar"; 但是如果这个字段已经存在的话, ...
- Windows安装VMware并在VMware中安装Ubuntu
安装 VMware 去官方下载 VMware 安装包,一路默认到底即可 VMware 安装Ubuntu 参考链接:VMware12安装虚拟机教程.Ubuntu16.04安装教程 VM1 ...
- Qt编写气体安全管理系统26-组态设计
一.前言 组态设计是应一个客户要求新增加进去的,设计理念就是在提供一个组态设计的初级功能,比如读取自定义控件动态库,加载所有的控件放到控件栏(有点类似qtcreator的控件栏)用户拖曳对应的控件到画 ...
- 判断命令test
判断命令test一般用于脚本当中,可以简写为中括号[ ].其会对跟随的条件进行判断,一般可以分为数值判断.字符串判断和文件判断.语法格式为test [判断条件]或[ 判断条件 ],注意中括号[ ]与判 ...
- Python - Django - form 组件基本用法
普通 form 表单的处理: reg.html: <!DOCTYPE html> <html lang="en"> <head> <met ...