当您改变视图的边界,或者将一个比例因子应用到视图的transform
属性声明时,边框矩形会发生等量的变化。根据内容模式的不同,视图的内容也可能被缩放或重新定位,以反映上述的变化。视图的contentMode
属性决定了边界变化和缩放操作作用到视图上产生的效果。缺省情况下,这个属性的值被设置为UIViewContentModeScaleToFill
,意味着视图内容总是被缩放,以适应新的边框尺寸。作为例子,图2-6显示了当视图的水平缩放因子放大一倍时产生的效果。
图2-6 使用scale-to-fill内容模式缩放视图
视图内容的缩放仅在首次显示视图的时候发生,渲染后的内容会被缓存在视图下面的层上。当边界或缩放因子发生变化时,UIKit并不强制视图进行重画,而是根据其内容模式决定如何显示缓存的内容。图2-7比较了在不同的内容模式下,改变视图边界或应用不同的比例缩放因子时产生的结果。
图2-7 内容模式比较
对视图应用一个比例缩放因子总是会使其内容发生缩放,而边界的改变在某些内容模式下则不会发生同样的结果。不同的UIViewContentMode
常量(比如UIViewContentModeTop
和UIViewContentModeBottomRight
)可以使当前的内容在视图的不同角落或沿着视图的不同边界显示,还有一种模式可以将内容显示在视图的中心。在这些模式的作用下,改变边界矩形只会简单地将现有的视图内容移动到新的边界矩形中对应的位置上。
当您希望在应用程序中实现尺寸可调整的控件时,请务必考虑使用内容模式。这样做可以避免控件的外观发生变形,以及避免编写定制的描画代码。按键和分段控件 (segmented control)特别适合基于内容模式的描画。它们通常使用几个图像来创建控件外观。除了有两个固定尺寸的盖帽图像之外,按键可以通过一个可伸展的、宽度 只有一个像素的中心图像来实现水平方向的尺寸调整。它将每个图像显示在自己的图像视图中,而将可伸展的中间图像的内容模式设置为UIViewContentModeScaleToFill
,使得在尺寸调整时两端的外观不会变形。更为重要的是,每个图像视图的关联图像都可以由Core Animation来缓存,因此不需要编写描画代码就可以支持动画,从而使大大提高了性能。
内容模式通常有助于避免视图内容的描画,但是当您希望对缩放和尺寸调整过程中的视图外观进行特别的控制时,也可以使用UIViewContentModeRedraw
模式。将视图的内容模式设置为这个值可以强制Core Animation使视图的内容失效,并调用视图的drawRect:
方法,而不是自动进行缩放或尺寸调整。
当您改变视图的边框矩形时,其内嵌子视图的位置和尺寸往往也需要改变,以适应原始视图的新尺寸。如果视图的autoresizesSubviews
属性声明被设置为YES
,则其子视图会根据autoresizingMask
属性的值自动进行尺寸调整。简单配置一下视图的自动尺寸调整掩码常常就能使应用程序得到合适的行为;否则,应用程序就必须通过重载layoutSubviews
方法来提供自己的实现。
设置视图的自动尺寸调整行为的方法是通过位OR操作符将期望的自动尺寸调整常量连结起来,并将结果赋值给视图的autoresizingMask
属性。表2-1列举了自动尺寸调整常量,并描述这些常量如何影响给定视图的尺寸和位置。举例来说,如果要使一个视图和其父视图左下角的相对位置保持不变,可以加入UIViewAutoresizingFlexibleRightMargin
和UIViewAutoresizingFlexibleTopMargin
常量,并将结果赋值给autoresizingMask
属性。当同一个轴向有多个部分被设置为可变时,尺寸调整的裕量会被平均分配到各个部分上。
自动尺寸调整掩码 |
描述 |
---|---|
|
这个常量如果被设置,视图将不进行自动尺寸调整。 |
|
这个常量如果被设置,视图的高度将和父视图的高度一起成比例变化。否则,视图的高度将保持不变。 |
|
这个常量如果被设置,视图的宽度将和父视图的宽度一起成比例变化。否则,视图的宽度将保持不变。 |
|
这个常量如果被设置,视图的左边界将随着父视图宽度的变化而按比例进行调整。否则,视图和其父视图的左边界的相对位置将保持不变。 |
|
这个常量如果被设置,视图的右边界将随着父视图宽度的变化而按比例进行调整。否则,视图和其父视图的右边界的相对位置将保持不变。 |
|
这个常量如果被设置,视图的底边界将随着父视图高度的变化而按比例进行调整。否则,视图和其父视图的底边界的相对位置将保持不变。 |
|
这个常量如果被设置,视图的上边界将随着父视图高度的变化而按比例进行调整。否则,视图和其父视图的上边界的相对位置将保持不变。 |
图2-8为这些常量值的位置提供了一个图形表示。如果这些常量之一被省略,则视图在相应方向上的布局就被固定;如果某个常量被包含在掩码中,在该方向的视图布局就就灵活的。
图2-8 视图的自动尺寸调整掩码常量
如果您通过Interface Builder配置视图,则可以用Size查看器的Autosizing控制来设置每个视图的自动尺寸调整行为。上图中的灵活宽度及高度常量和 Interface Builder中位于同样位置的弹簧具有同样的行为,但是空白常量的行为则是正好相反。换句话说,如果要将灵活右空白的自动尺寸调整行为应用到 Interface Builder的某个视图,必须使相应方向空间的Autosizing控制为空,而不是放置一个支柱。幸运的是,Interface Builder通过动画显示了您的修改对视图自动尺寸调整行为的影响。
如果视图的autoresizesSubviews
属性被设置为NO
,则该视图的直接子视图的所有自动尺寸调整行为将被忽略。类似地,如果一个子视图的自动尺寸调整掩码被设置为UIViewAutoresizingNone
,则该子视图的尺寸将不会被调整,因而其直接子视图的尺寸也不会被调整。
请注意:为了使自动尺寸调整的行为正确,视图的transform
属性必须设置为恒等变换;其它变换下的尺寸自动调整行为是未定义的。
自动尺寸调整行为可以适合一些布局的要求,但是如果您希望更多地控制视图的布局,可以在适当的视图类中重载layoutSubviews
方法。有关视图布局管理的更多信息,请参见“响应布局的变化”部分。
管理用户界面的视图层次是开发应用程序用户界面的关键部分。视图的组织方式不仅定义了应用程序的视觉外观,而且还定义了应用程序如何响应变化。视图层次中 的父-子关系可以帮助我们定义应用程序中负责处理触摸事件的对象链。当用户旋转设备时,父-子关系也有助于定义每个视图的尺寸和位置是如何随着界面方向的 变化而变化的。
图2-9显示了一个简单的例子,说明如何通过视图的分层来创建期望的视觉效果。在Clock程序中,页签条和导航条视图,以及定制视图混合在一起,实现了整个界面。
图2-9 Clock程序的视图层
如果您探究Clock程序中视图之间的关系,就会发现它们很像“改变视图的层”部分中显示的关系,窗口对象是应用程序的页签条、导航条、和定制视图的根视图。
图2-10 Clock程序的视图层次
在iPhone应用程序的开发过程中,有几种建立视图层次的方法,包括基于Interface Builder的可视化方法和通过代码编程的方法。本文的下面部分将向您介绍如何装配视图层次,以及如何在建立视图层次之后寻找其中的视图,还有如何在不 同的视图坐标系统之间进行转换。
创建视图对象的最简单方法是使用Interface Builder进行制作,然后将视图对象从作成的nib文件载 入内存。在Interface Builder的图形环境中,您可以将新的视图从库中拖出,然后放到窗口或另一个视图中,以快速建立需要的视图层次。Interface Builder使用的是活的视图对象,因此,当您用这个图形环境构建用户界面时,所看到的就是运行时装载的外观,而且不需要为视图层次中的每个视图编写单 调乏味的内存分配和初始化代码。
如果您不喜欢Interface Builder和nib文件,也可以通过代码来创建视图。创建一个新的视图对象时,需要为其分配内存,并向该对象发送一个initWithFrame:
消息,以对其进行初始化。举例来说,如果您要创建一个新的UIView
类的实例作为其它视图的容器,则可以使用下面的代码:
CGRect viewRect = CGRectMake(0, 0, 100, 100); |
UIView* myView = [[UIView alloc] initWithFrame:viewRect]; |
请注意:虽然所有系统提供的视图对象都支持initWithFrame:
消息,但是其中的一部分可能有自己偏好的初始化方法,您应该使用那些方法。有关定制初始化方法的更多信息,请参见相应的类参考文档。
您在视图初始化时指定的边框矩形代表该视图相对于未来父视图的位置和大小。在将视图显示于屏幕上之前,您需要将它加入到窗口或其它视图中。在这个时候,UIKit会根据您指定的边框矩形将视图放置到其父视图的相应位置中。有关如何将视图添加到视图层次的信息,请参见“添加和移除子视图”部分。
Interface Builder是建立视图层次的最便利工具,因为它可以让您看到视图在运行时的外观。在界面制作完成后,它将视图对象及其层次关系保存在nib文件中。在 运行时,系统会按照nib文件的内容为应用程序重新创建那些对象和关系。当一个nib文件被装载时,系统会自动调用重建视图层次所需要的UIView
方法。
如果您不喜欢通过Interface Builder和nib文件来创建视图层次,则可以通过代码来创建。如果一个视图必须具有某些子视图才能工作,则应该在其initWithFrame:
方法中进行对其创建,以确保子视图可以和视图一起被显示和初始化。如果子视图是应用程序设计的一部分(而不是视图工作必需的),则应该在视图的初始化代码之外进行创建。在iPhone程序中,有两个地方最常用于创建视图和子视图,它们是应用程序委托对象的applicationDidFinishLaunching:
方法和视图控制器的loadView
方法。
您可以通过下面的方法来操作视图层次中的视图对象:
调用父视图的addSubview:
方法来添加视图,该方法将一个视图添加到子视图列表的最后。
调用父视图的insertSubview:...
方法可以在父视图的子视图列表中间插入视图。
调用父视图的bringSubviewToFront:
、sendSubviewToBack:
、或exchangeSubviewAtIndex:withSubviewAtIndex:
方法可以对父视图的子视图进行重新排序。使用这些方法比从父视图中移除子视图并再次插入要快一些。
调用子视图(而不是父视图)的removeFromSuperview
方法可以将子视图从父视图中移除。
在添加子视图时,UIKit会根据子视图的当前边框矩形确定其在父视图中的初始位置。您可以随时通过修改子视图的frame
属性声明来改变其位置。缺省情况下,边框位于父视图可视边界外部的子视图不会被裁剪。如果您希望激活裁剪功能,必须将父视图的clipsToBounds
属性设置为YES
。
程序清单2-1显示了一个应用程序委托对象的applicationDidFinishLaunching:
方法示例。在这个例子中,应用程序委托在启动时通过代码创建全部的用户界面。界面中包含两个普通的UIView
对象,用于显示基本颜色。每个视图都被嵌入到窗口中,窗口也是UIView
的一个子类,因此可以作为父视图。父视图会保持它们的子视图,因此这个方法释放了新创建的视图对象,以避免重复保持。
程序清单2-1 创建一个带有视图的窗口
- (void)applicationDidFinishLaunching:(UIApplication *)application { |
// Create the window object and assign it to the |
// window instance variable of the application delegate. |
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; |
window.backgroundColor = [UIColor whiteColor]; |
|
// Create a simple red square |
CGRect redFrame = CGRectMake(10, 10, 100, 100); |
UIView *redView = [[UIView alloc] initWithFrame:redFrame]; |
redView.backgroundColor = [UIColor redColor]; |
|
// Create a simple blue square |
CGRect blueFrame = CGRectMake(10, 150, 100, 100); |
UIView *blueView = [[UIView alloc] initWithFrame:blueFrame]; |
blueView.backgroundColor = [UIColor blueColor]; |
|
// Add the square views to the window |
[window addSubview:redView]; |
[window addSubview:blueView]; |
|
// Once added to the window, release the views to avoid the |
// extra retain count on each of them. |
[redView release]; |
[blueView release]; |
|
// Show the window. |
[window makeKeyAndVisible]; |
} |
重要提示:在内存管理方面,可以将子视图考虑为其它的集合对象。特别是当您通过addSubview:
方法将一个视图作为子视图插入时,父视图会对其进行保持操作。反过来,当您通过removeFromSuperview
方法将子视图从父视图移走时,子视图会被自动释放。在将视图加入视图层次之后释放该对象可以避免多余的保持操作,从而避免内存泄露。
有关Cocoa内存管理约定的更多信息,请参见Cocoa内存管理编程指南。
当您为某个视图添加子视图时,UIKit会向相应的父子视图发送几个消息,通知它们当前发生的状态变化。您可以在自己的定制视图中对诸如willMoveToSuperview:
、willMoveToWindow:
、willRemoveSubview:
、didAddSubview:
、didMoveToSuperview
、和didMoveToWindow
这样的方法进行重载,以便在事件发生的前后进行必要的处理,并根据发生的变化更新视图的状态信息。
在视图层次建立之后,您可以通过视图的superview
属性来取得其父视图,或者通过subviews
属性取得视图的子视图。您也可以通过isDescendantOfView:
方法来判定一个视图是否在其父视图的视图层中。一个视图层次的根视图没有父视图,因此其superview
属性被设置为nil
。对于当前被显示在屏幕上的视图,窗口对象通常是整个视图层次的根视图。
您可以通过视图的window
属性来取得指向其父窗口(如果有的话)的指针,如果视图还没有被链接到窗口上,则该属性会被设置为nil
。