加深理解UIView,UIResponder,UIController

读完这篇文章后 觉得自己对UIView UIResponder 和UIController的理解瞬间增加了一个层次,记下笔记,留给我这忘事精随时查看

视图层次概览

如果你观察一下 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;= / 2;= 3;= [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 机制触发事件,如下面的例子:

[];

为了响应触摸,你可能更倾向于使用手势识别。然而如果想要更接近底层,仍然可以重写touchesBegan,touchesMoved和touchesEnded方法来访问原始的触摸行为。但虽说如此,创建一个手势识别的子类来把手势处理相关的逻辑从你的视图或者视图控制器中分离出来,在很多情况下都是一种更合适的方式。

创建自定义控件时所面对的一个普遍的设计问题是向拥有它们的类中回传返回值。比如,假设你创建了一个绘制交互饼状图的自定义控件,想知道用户何时选择了其中一个部分。你可以用很多种不同的方法来解决这个问题,比如通过 target action 模式,代理,block 或者 KVO,甚至通知。

使用 Target-Action

经典学院派的,通常也是最方便的做法是使用 target-action。在用户选择后你可以在自定义的视图中做类似这样的事情:

[];

如果有一个视图控制器在管理这个视图,需要这样做:

– (void)setupPieChart{[self.pieChart addTarget:selfaction:@selector(updateSelection:)forControlEvents:UIControlEventValueChanged];}- (void)updateSelection:(id)sender{);}

这么做的好处是在自定义视图子类中需要做的事情很少,并且自动获得多目标支持。

使用代理

如果你需要更多的控制从视图发送到视图控制器的消息,通常使用代理模式。在我们的饼状图中,代码看起来大概是这样:

[.selectedSector];人生就是一场旅行,不在乎目的地,

加深理解UIView,UIResponder,UIController

相关文章:

你感兴趣的文章:

标签云: