本节将为Hypnosister应用添加一个UIScrollView对象,使其成为应用窗口的子视图,然后再将BNRHypnosisView作为子视图加入UIScrollView对象(见图5-2)。
图5-2 加入UIScrollView之后的视图层次结构
通常情况下,UIScrollView对象适用于那些尺寸大于屏幕的视图。当某个视图是UIScrollView对象的子视图时,该对象会画出该视图的某块区域(形状为矩形)。当用户按住这块矩形区域并移动手指(即拖动,pan)时,UIScrollView对象会改变该矩形所显示的子视图区域。读者可以将UIScrollView对象看成是镜头,而其子视图是拍摄的景观。这里移动的是“镜头”,而不是“景观”(见图5-3)。UIScrollView对象的尺寸就是这个“镜头”的尺寸,而其能够拍摄的范围是由其属性contentSize决定的。通常情况下,contentSize的数值就是子视图的尺寸。
UIScrollView是UIView的子类,同样可以使用initWithFrame:消息初始化,还可以将其作为子视图添加到其他视图中。
图5-3 UIScrollView对象及其contentSize
在BNRAppDelegate.m中,创建一个有着超大尺寸的BNRHypnosisView对象并将其加入一个UIScrollView对象,然后将这个UIScrollView对象加入窗口,代码如下:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
// 在这里添加应用启动后的初始化代码
CGRect firstFrame = self.window.bounds;
BNRHypnosisView *firstView = [[BNRHypnosisView alloc]
initWithFrame:firstFrame];
[self.window addSubview:firstView];
// 创建两个CGRect结构分别作为UIScrollView对象和BNRHypnosisView对象的frame
CGRect screenRect = self.window.bounds;
CGRect bigRect = screenRect;
bigRect.size.width *= 2.0;
bigRect.size.height *= 2.0;
// 创建一个UIScrollView对象,将其尺寸设置为窗口大小
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
[self.window addSubview:scrollView];
// 创建一个有着超大尺寸的BNRHypnosisView对象并将其加入UIScrollView对象
BNRHypnosisView *hypnosisView = [[BNRHypnosisView alloc]
initWithFrame:bigRect];
[scrollView addSubview:hypnosisView];
// 告诉UIScrollView对象/"取景/"范围有多大
scrollView.contentSize = bigRect.size;
self.window.backgroundColor = [UIColor whiteColor];
构建并运行应用,可以上、下、左、右拖动来查看超大尺寸的BNRHypnosisView对象的其余部分(见图5-4)。
图5-4 BNRHypnosisView的右上角
移动BNRHypnosisView对象的同时,圆形的颜色也会发生变化。这是由于移动视图时也会产生触摸事件,因此运行循环会将触摸事件发送给UIScrollView和BNRHypnosisView。第13章会介绍如何检测和处理“tap(点击)”手势,用来区别触摸手势和移动手势。
使用UIScrollView还可以实现“Pinch-to-zoom(捏合缩放)”功能。虽然只需要添加几行代码就可以实现该功能,但是这些代码涉及第7章中的技术。为Hypnosister添加捏合缩放功能是第7章的练习。
拖动与分页
UIScrollView对象还可以滑动显示所有加入UIScrollView对象的子视图。
在BNRAppDelegate.m中,将BNRHypnosisView对象的尺寸改回与屏幕的尺寸相同,然后再创建一个BNRHypnosisView对象,将其尺寸也设置为与屏幕的尺寸相同并加入UIScrollView对象。此外,还要将UIScrollView对象的contentSize的宽度设置为屏幕宽度的2倍,高度不变,代码如下:
// 创建两个CGRect结构分别作为UIScrollView对象和BNRHypnosisView对象的frame
CGRect screenRect = self.window.bounds;
CGRect bigRect = screenRect;
bigRect.size.width *= 2.0;
bigRect.size.height *= 2.0;
// 创建一个UIScrollView对象,将其尺寸设置为窗口大小
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
[self.window addSubview:scrollView];
// 创建一个有着超大尺寸的BNRHypnosisView对象并将其加入UIScrollView对象
BNRHypnosisView *hypnosisView = [[BNRHypnosisView alloc]
initWithFrame:bigRect];
// 创建一个大小与屏幕相同的BNRHypnosisView对象并将其加入UIScrollView对象
BNRHypnosisView *hypnosisView = [[BNRHypnosisView alloc]
initWithFrame:screenRect];
[scrollView addSubview:hypnosisView];
// 创建第二个大小与屏幕相同的BNRHypnosisView对象并放置在第一个BNRHypnosisView
// 对象的右侧,使其刚好移出屏幕外
screenRect.origin.x += screenRect.size.width;
BNRHypnosisView *anotherView = [[BNRHypnosisView alloc]
initWithFrame:screenRect];
[scrollView addSubview:anotherView];
// 告诉UIScrollView对象/"取景/"范围有多大
scrollView.contentSize = bigRect.size;
构建并运行应用,按从左向右的方向拖动屏幕,应该能先后看到两个BNRHypnosisView对象。请注意,UIScrollView对象有可能同时显示两个BNRHypnosisView对象的部分区域,即两个BNRHypnosisView对象的连接部分(见图5-5)。
图5-5 UIScrollView显示两个BNRHypnosisView的连接部分
某些情况下,这种行为是符合预期的。但是有时也要让UIScrollView对象的“镜头”的边和其显示的某个视图的边对齐。为此,要将UIScrollView对象的pagingEnabled设置为YES。在BNRAppDelegate.m中将scrollView的pagingEnabled设置为YES,代码如下:
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
[scrollView setPagingEnabled:YES];
[[self window] addSubview:scrollView];
构建并运行应用,拖动屏幕直到能看到两个BNRHypnosisView对象的连接部分,然后松开手指。UIScrollView对象应该会自动将“镜头”切换到其中一个BNRHypnosisView对象上。UIScrollView对象的分页实现原理是:UIScrollView对象会根据其bounds的尺寸,将contentSize分割为尺寸相同的多个区域。拖动结束后,UIScrollView实例会自动滚动并只显示其中的一个区域。