视图:重绘与UIScrollView
接上上一章学习的Hypnosister应用,我们继续将他功能做的复杂生动:当用户触摸HypnosisView时,颜色会发生变化。
首先要在HypnosisView中声明一个属性,用来表示圆形的颜色。此处我们不在头文件中声明属性,而在类扩展(class extension)中声明。
@interface HypnosisterView()
@property (strong, nonatomic) UIColor *circleColor;
@end
```这三行代码称为HypnosisView的类扩展。类扩展中声明了一个circleColor属性,那么为什么要将该属性声明在类扩展中而不是头文件中呢?原因稍后介绍。
构建并运行应用,HypnosisView对象绘制的圆形颜色应该之前的相同。下一步是编写视图被触摸时改变圆形颜色的代码。
当用户触摸视图时,视图会收到touchedBegan:withEvent消息,该消息用来处理触摸事件。
<div class="se-preview-section-delimiter"></div>
##运行循环和重绘视图
iOS应用启动时会开始一个运行循环(run loop)。运行循环的工作是**监听事件**,当事件发生时,运行循环为相应的事件找到合适的处理方法。这些处理方法会调用其他方法,而这些方法又会调用更多其他方法,以此类推。只有当这些都执行完毕时,控制权才会再次回到运行循环。
当应用将控制权交给运行循环时,运行循环首先会检查是否有等待重绘的视图,然后向所有等待重绘的视图发送drawRect:消息,最后视图结构中所有视图的图层再次组合成一幅完整的图像并绘制到屏幕上。
iOS做了两方面优化来保证用户界面的流畅性----不重绘显示的内容没有改变的视图;在每次事件处理周期(event handing cycle)中只发送一次drawRect:消息。在事件处理周期中,视图属性可能会发生多次改变,如果视图在每次属性改变时都重绘自己,就会减慢界面的响应速度。iOS辉仔运行循环的最后阶段集中处理所有需要重绘的视图,尤其是对于属性发生多次改变的视图,在每次事件处理周期中只重绘一次。
<div class="se-preview-section-delimiter"></div>
##类扩展
为什么我们将circleColor属性声明在HypnosisView的类扩展中?那么将属性声明在头文件和类扩展中有什么区别?
头文件是一个类的“用户手册”,其他类可以通过头文件知道该类的功能和使用方法。使用头文件的目的是向其他类公开该类声明的属性和方法,也就是说头文件中声明的属性和方法对其他类是可见的(visible)。
在类扩展中声明类的内部属性和方法是良好的编程习惯,这样做可以保持头文件的精简,避免内部实现细节的暴露。
在语法上,类扩展中声明方法与头文件类似,需要使用@interface指令,后面跟类名,接着一对空括号。**子类无法访问父类在类扩展中声明的属性和方法。**
Tips:有时需要让其他开发者了解类的某些内部属性,以便更好地理解类的工作原理和使用方法。可以在另一个文件中声明类扩展,并将该文件导入类的实现文件。
<div class="se-preview-section-delimiter"></div>
##使用UISrcrollView
通常情况下,UIScrollView对象适用于那些大于屏幕的视图。当某个视图是UIScrollView对象的子视图时,该对象会画出该视图的某块区域。当用户按住这块矩形区域并移动手指时,UIScrollView对象会改变矩形所显示的子视图区域。
UIScrollView是UIView的子类,同样可以使用initWithFrame:消息初始化,还可以将其作为子视图添加到其他视图中。
<div class="se-preview-section-delimiter"></div>
// 创建两个CGRect结构分别作为UIScrollView对象和HypnosisView对象的frame
CGRect screenRect = self.window.bounds;
CGRect bigRect = screenRect;
bigRect.size.width = screenRect.size.width * 2.0;
bigRect.size.height = screenRect.size.height * 2.0;
// 创建一个UIScrollView对象,将其尺寸设置为窗口大小
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
[self.window addSubview:scrollView];
// 创建一个有着超大尺寸的HypnosisView对象加入UIScrollView对象
HypnosisterView *hypnosisView = [[HypnosisterView alloc] initWithFrame:bigRect];
[self.window addSubview:hypnosisView];
[scrollView addSubview:hypnosisView];
//告诉UIScrollView对象“取景”范围多大
scrollView.contentSize = bigRect.size;
“`
其中有关UIScrollView有几点要说的,initWithFrame:方法定义了“镜头”的大小,contentSize属性表示“景观”的大小。
// 创建两个CGRect结构分别作为UIScrollView对象和HypnosisView对象的frame
CGRect screenRect = self.window.bounds;
CGRect bigRect = screenRect;
bigRect.size.width = screenRect.size.width * 2.0;
bigRect.size.height = screenRect.size.height * 2.0;
// 创建一个UIScrollView对象,将其尺寸设置为窗口大小
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
[self.window addSubview:scrollView];
// 创建一个有着超大尺寸的HypnosisView对象加入UIScrollView对象
HypnosisterView *hypnosisView = [[HypnosisterView alloc] initWithFrame:bigRect];
[self.window addSubview:hypnosisView];
[scrollView addSubview:hypnosisView];
//告诉UIScrollView对象“取景”范围多大
scrollView.contentSize = bigRect.size;
其中有关UIScrollView有几点要说的,initWithFrame:方法定义了“镜头”的大小,contentSize属性表示“景观”的大小。
拖动和分页
UIScrollView对象还可以滑动显示所有加入UIScrollView对象的子视图。
[scrollView setPagingEnabled:YES];
是的只会显示一个page,不会出现显示一半这个page,一般另一个page。
UIScrollView对象的分页实现原理:UIScrollView对象会根据其bounds的尺寸,将contentSize分割为尺寸相同的多个区域。拖动结束后,UIScrollView实例会自动滚动并只显示其中的一个区域。