Custom Controls
In this article we will look at tips and tricks to write custom views and controls. We will start with an overview of what UIKit provides us already, and see some tricks for rendering. We will dive into communication strategies between views and their owners, and very briefly look at accessibility, localization and testing.
Overview of the View Hierarchy
If you look at any UIView subclass, you will see three base classes: responders, views, and controls. Wersquo;ll quickly go over all three to see what is going on.
UIResponder
The UIResponder class is the superclass of UIView. A responder can handle events such as touches, motion, and remote control events. The reason that this is a separate class, and not merged into UIView, is that there are more subclasses of UIResponder, most notably UIApplication and UIViewController. By overriding the methods in UIResponder, a class can determine whether it can become a first responder (i.e. the currently focused element for input).
When interface events happen, such as touches or motion, they get sent to the first responder (often, this is a view). When the event does not get handled by the first responder, it goes up the responder chain to the view controller, and if it still doesnrsquo;t get handled, it continues to the application. If you want to detect a shake gesture, you could do this in all three of these levels, depending on your needs.
The UIResponder also lets you customize the input methods, from adding an accessory view to the keyboard with inputAccessoryView to providing a completely custom keyboard by using inputView.
UIView
The UIView subclass handles everything related to drawing content and handling touches. Anybody who has built a “Hello, World” app knows about views, but letrsquo;s reiterate some of the tricky bits:
A common misconception is that this area is defined by the viewrsquo;s frame. In reality, the frame is actually a property that is derived, most notably from the combination of center and bounds. When not doing Auto Layout, most people use the frame to position and size the view. Be warned, because the documentation spells out a caveat:
If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
One of the other things that lets you add interactivity to views is gesture recognizers. Note that they donrsquo;t work on responders, but instead only on views and their subclasses.
UIControl
Building on views, the UIControl class adds more support for interactivity. Most importantly, it adds the target/action pattern. Looking at the concrete subclasses, we can see buttons, date pickers, text fields, and more. When creating interactive controls, you often want to subclass a descendant of UIControl. Some notable classes that are not controls are bar buttons (although they do support target/action) and text views (here, getting notified requires you to use a delegate).
Rendering
Now, letrsquo;s move on to the visual bit: custom rendering. As Daniel mentioned in his article, you probably want to avoid doing rendering on the CPU, but instead offload it to the GPU. There is one rule of thumb to achieve this: try to avoid drawRect:, and instead compose your custom views out of existing views.
Often, the quickest way to render something is just by using image views. For example, letrsquo;s suppose that you want to draw round avatars and a border, such as in the picture below:
To achieve this, we created an image view subclass with the following code:
// called from initializer
- (void)setupView
{
self.clipsToBounds = YES;
self.layer.cornerRadius = self.bounds.size.width / 2;
self.layer.borderWidth = 3;
self.layer.borderColor = [UIColor darkGrayColor].CGColor;
}
I would like to encourage you to dive into CALayer and its properties, because most of what you can achieve with that will be faster than drawing your own things using Core Graphics. Nonetheless, as always, it is important to profile your code.
By using stretchable images together with your image views, you can also greatly improve performance. In a post called Taming UIButton, Reda Lemeden explores different ways of drawing. At the end of the article therersquo;s a nugget of gold: a link to a comment by Andy Matuschak on Hacker News, explaining which is the fastest of these techniques: a resizable image. The reason is because a resizable image takes a minimum amount of data transfer between the CPU and GPU, and the drawing of these images is highly optimized.
If you are processing images, you can also often get away with letting the GPU do that for you, instead of doing it with Core Graphics. Using Core Image, you can create complicated effects on images without having to do any rendering on the CPU. You can render directly to an OpenGL context, and everything will happen on the GPU.
Custom Drawing
If you do decide to do custom drawing, there are several different options you can choose from. If possible, see if you can generate an image, and then cache that, either on disk or in memory. If your content is very dynamic, you can maybe use Core Animation, or if it doesnrsquo;t work, go for Core Graphics. If you really want to get close to the metal, it is not that hard to use GLKit and raw OpenGL, but it does require a lot of work.
If you do choose to override drawRect:, make sure to take a look at content modes. The default mode scales the content to fill the viewrsquo;s bounds, and does not get redrawn when the frame changes.
Custom Interaction
As said, when writing custom controls, you almost always want to extend a subclass of UIControl. In your subclass, you can fire events using the target action me
剩余内容已隐藏,支付完成后下载完整资料
自定义控件
本文将讨论一些自定义视图、控件的诀窍和技巧。我们先概述一下 UIKit 向我们提供的控件,并介绍一些渲染技巧。随后我们会深入到视图和其所有者之间的通信策略,并简略探讨辅助功能,本地化和测试。
视图层次概览
如果你观察一下 UIView 的子类,可以发现 3 个基类: reponders (响应者),views (视图)和 controls (控件)。我们快速重温一下它们之间发生了什么。
UIResponder
UIResponder 是 UIView 的父类。responder 能够处理触摸、手势、远程控制等事件。之所以它是一个单独的类而没有合并到 UIView 中,是因为 UIResponder 有更多的子类,最明显的就是 UIApplication 和 UIViewController。通过重写 UIResponder 的方法,可以决定一个类是否可以成为第一响应者 (first responder),例如当前输入焦点元素。
当 touches (触摸) 或 motion (指一系列运动传感器) 等交互行为发生时,它们被发送给第一响应者 (通常是一个视图)。如果第一响应者没有处理,则该行为沿着响应链到达视图控制器,如果行为仍然没有被处理,则继续传递给应用。如果想监测晃动手势,可以根据需要在这3层中的任意位置处理。
UIResponder 还允许自定义输入方法,从 inputAccessoryView 向键盘添加辅助视图到使用 inputView 提供一个完全自定义的键盘。
UIView
UIView 子类处理所有跟内容绘制有关的事情以及触摸时间。只要写过 'Hello, World' 应用的人都知道视图,但我们重申一些技巧点:
一个普遍错误的概念:视图的区域是由它的 frame 定义的。实际上 frame 是一个派生属性,是由 center 和 bounds 合成而来。不使用 Auto Layout 时,大多数人使用 frame 来改变视图的位置和大小。小心些,官方文档特别详细说明了一个注意事项:
如果 transform 属性不是 identity transform 的话,那么这个属性的值是未定义的,因此应该将其忽略
另一个允许向视图添加交互的方法是使用手势识别。注意它们对 responders 并不起作用,而只对视图及其子类奏效。
UIControl
UIControl 建立在视图上,增加了更多的交互支持。最重要的是,它增加了 target / action 模式。看一下具体的子类,我们可以看一下按钮,日期选择器 (Date pickers),文本框等等。创建交互控件时,你通常想要子类化一个 UIControl。一些常见的像 bar buttons (虽然也支持 target / action) 和 text view (这里需要你使用代理来获得通知) 的类其实并不是 UIControl。
渲染
现在,我们转向可见部分:自定义渲染。正如 Daniel 在他的文章中提到的,你可能想避免在 CPU 上做渲染而将其丢给 GPU。这里有一条经验:尽量避免 drawRect:,使用现有的视图构建自定义视图。
通常最快速的渲染方法是使用图片视图。例如,假设你想画一个带有边框的圆形头像,像下面图片中这样:
为了实现这个,我们用以下的代码创建了一个图片视图的子类:
// called from initializer
- (void)setupView
{
self.clipsToBounds = YES;
self.layer.cornerRadius = self.bounds.size.width / 2;
self.layer.borderWidth = 3;
self.layer.borderColor = [UIColor darkGrayColor].CGColor;
}
我鼓励各位读者深入了解 CALayer 及其属性,因为你用它能实现的大多数事情会比用 Core Graphics 自己画要快。然而一如既往,监测自己的代码的性能是十分重要的。
把可拉伸的图片和图片视图一起使用也可以极大的提高效率。在 Taming UIButton 这个帖子中,Reda Lemeden 探索了几种不同的绘图方法。在文章结尾处有一个很有价值的来自 UIKit 团队的工程师 Andy Matuschak 的回复,解释了可拉伸图片是这些技术中最快的。原因是可拉伸图片在 CPU 和 GPU 之间的数据转移量最小,并且这些图片的绘制是经过高度优化的。
处理图片时,你也可以让 GPU 为你工作来代替使用 Core Graphics。使用 Core Image,你不必用 CPU 做任何的工作就可以在图片上建立复杂的效果。你可以直接在 OpenGL 上下文上直接渲染,所有的工作都在 GPU 上完成。
自定义绘制
如果决定了采用自定义绘制,有几种不同的选项可供选择。如果可能的话,看看是否可以生成一张图片并在内存和磁盘上缓存起来。如果内容是动态的,也许你可以使用 Core Animation,如果还是行不通,使用 Core Graphics。如果你真的想要接近底层,使用 GLKit 和原生 OpenGL 也不是那么难,但是需要做很多工作。
如果你真的选择了重写 drawRect:,确保检查内容模式。默认的模式是将内容缩放以填充视图的范围,这在当视图的 frame 改变时并不会重新绘制。
自定义交互
正如之前所说的,自定义控件的时候,你几乎一定会扩展一个 UIControl 的子类。在你的子类里,可以使用 target action 机制触发事件,如下面的例子:
[self sendActionsForControlEvents:UIControlEventValueChanged];
为了响应触摸,你可能更倾向于使用手势识别。然而如果想要更接近底层,仍然可以重写 touchesBegan, touchesMoved 和 touchesEnded 方法来访问原始的触摸行为。但虽说如此,创建一个手势识别的子类来把手势处理相关的逻辑从你的视图或者视图控制器中分离出来,在很多情况下都是一种更合适的方式。
创建自定义控件时所面对的一个普遍的设计问题是向拥有它们的类中回传返回值。比如,假设你创建了一个绘制交互饼状图的自定义控件,想知道用户何时选择了其中一个部分。你可以用很多种不同的方法来解决这个问题,比如通过 target action 模式,代理,block 或者 KVO,甚至通知。
使用 Target-Action
经典学院派的,通常也是最方便的做法是使用 target-action。在用户选择后你可以在自定义的视图中做类似这样的事情:
[self sendActionsForControlEvents:UIControlEventValueChanged];
如果有一个视图控制器在管理这个视图,需要这样做:
- (void)setupPieChart
{
[self.pieChart addTarget:self
action:@selector(updateSelection:)
forControlEvents:UIControlEventValueChanged];
}
- (void)updateSelection:(id)sender
{
NSLog(@'%@', self.pieChart.selectedSector);
}
这么做的好处是在自定义视图子类中需要做的事情很少,并且自动获得多目标支持。
使用代理
如果你需要更多的控制从视图发送到视图控制器的消息,通常使用代理模式。在我们的饼状图中,代码看起来大概是这样:
[self.delegate pieChart:self didSelectSector:self.selectedSector];
在视图控制器中,你要写如下代码:
@interface MyViewController lt;PieChartDelegategt;
...
- (void)setupPieChart
{
self.pieChart.delegate = self;
}
- (void)pieChart:(PieChart*)pieChart didSelectSector:(PieChartSector*)sector
{
// 处理区块
}
当你想要做更多复杂的工作而不仅仅是通知所有者值发生了变化时,这么做显然更合适。不过虽然大多数开发人员可以非常快速的实现自定义代理,但这种方式仍然有一些缺点:你必须检查代理是否实现了你想要调用的方法 (使用 respondsToSelector:),最重要的,通常你只有一个代理 (或者需要创建一个代理数组)。也就是说,一旦视图所有者和视图之间的通信变得稍微复杂,我们几乎总是会采取这种模式。
使用 Block
另一个选择是使用 block。再一次用饼状图举例,代码看起来大概是这样:
@interface PieChart : UIControl
@property (nonatomic,copy) void(^selectionHandler)(PieChartSection* selectedSection);
@end
在选取行为的代码中,你只需要执行它。在此之前检查一下block是否被赋值非常重要,因为执行一个未被赋值的 block 会使程序崩溃。
if (self.selectionHandler != NULL) {
self.selectionHandler(self.selectedSection);
}
这种方法的好处是可以把相关的代码整合在视图控制器中:
- (void)setupPieChart
{
self.pieChart.selectionHandler = ^(PieChartSection* section) {
// 处理区块
}
}
就像代理,每个动作通常只有一个 block。另一个重要的限制是不要形成引用循环。如果你的视图控制器持有饼状图的强引用,饼状图持有 block,block 又持有视图控制器,就形成了一个引用循环。只要在 block 中引用 self 就会造成这个错误。所以通常代码会写成这个样子:
__weak id weakSelf = self;
self.pieChart.selectionHandler = ^(PieChartSection* section) {
MyViewController* strongSelf = weakSelf;
[strongSelf handleSectionChange:section];
}
一旦 block 中的代码要失去控制 (比如 block 中要处理的事情太多,导致 block 中的代码过多),你还应该将它们抽离成独立的方法,这种情况的话可能用代理会更好一些。
使用 KVO
如果喜欢 KVO,你也可以用它来观察。这有一点神奇而且没那么直接,但当应用中已经使用,它是很好的解耦设计模式。在饼状图类中,编写代码:
self.selectedSegment = theNewSelectedSegment;
当使用合成属性,KVO 会拦截到该变化并发出通知。在视图控制器中,编写类似的代码:
- (void)setupPieChart
{
[self.pieChart addObserver:self forKeyPath:@'selectedSegment' options:0 context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if(object == self.pieChart amp;amp; [keyPath isEqualToString:@'selectedSegment']) {
// 处理改变
}
}
根据你的需要,在 viewWillDisa
剩余内容已隐藏,支付完成后下载完整资料
资料编号:[137222],资料为PDF文档或Word文档,PDF文档可免费转换为Word
课题毕业论文、外文翻译、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。