6.1 界面的管理
iOS创意程序设计家
UIViewController提供了对于iOS应用程序的基本界面管理模式,包括界面的呈现、Modal窗口界面的管理以及iOS设备旋转后界面旋转的支持等。
每个UIViewController都会对应到一个唯一的UIView,这表示这个界面由相对应的UIViewCon-
troller所控制。如同我们在前一章所提到的,在UIViewController类中,有一个view的属性,而UIViewController正是通过这个属性才得以与其管理的界面关联起来。在iPhone与iPod Touch中,这个关联的底层界面通常占有整个屏幕的大小;然而在iPad中,这个底层界面可能只占一小部分而已。
除了对界面的管理外,其实UIViewController跟UIView一样,都是UIResponder这个类的子类。因为UIViewController也会负责事件的处理,事实上,在大部分时候,我们也都会用UIViewController来处理这些事件。
iOS SDK为不同的界面需求提供了相对应的界面控制器。例如,为了在界面上显示表格控件UITable-
View,就必须使用相对应的界面控制器UITableViewController;为了做出导航栏的应用程序,就必须使用UINavigationController等。此外,在iOS应用程序中,界面的切换通常也伴随着界面控制器的切换,在本章后续的内容里面,我们会提到如何做到这一点。
6.1.1 界面方向的管理
界面控制器也同时控制着界面方向的改变。例如,当我们在使用Safari来浏览网页的时候,界面会随着手机方向而改变,这就是界面控制器在起作用。如果要让手机可以支持方向的改变,那么应该去改写UIViewController里面的shouldAutorotateToInterfaceOrientation:这个方法。如果希望手机可以支持“Home”键在左边的风景模式以及“Home”键在右边的风景模式,那么可以将这个方法改写如下。
-(BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) orientation {
return (orientation==UIInterfaceOrientationLandscapeLeft) ||
(orientation== UIInterfaceOrientationLandscapeRight);
}
其他可能的方向:
UIInterfaceOrientationPortrait:肖像模式。
UIInterfaceOrientationPortraitUpsideDown:“Home”键上的肖像模式。
6.1.2 内存使用的管理
由于界面控制器控制着它所使用的界面,在内存不足的时候,界面控制器的didReceiveMemoryWarning方法就会被调用,这时候您可以在这个方法内释放不需要使用的资源,这其中包括界面控件。对于iOS 3.0之后的版本,您应该在viewDidUnload方法中释放掉不需要使用的界面控件。如果在界面控制器中包括了一些Outlet的使用,那么您也应该在这个方法中将这些Outlet设置为nil以便释放它们。
6.1.3 界面的加载流程
在设计界面上,所有的界面都是以View Controller为单位的。也就是说,不管使用故事板或是XIB来产生界面,每一个手机的界面背后都有一个View Controller来控制着界面。因此,当系统加载了这些.xib或.storyboard的文件后,其实也一并加载了界面控制器。这个加载的过程跟我们日后编写程序的时候有很大的关联性,所以一定得知道这一过程中发生了哪些事情。
从UIViewController类的定义里面,我们可以得知它遵循着NSCoding这个协议。而这个协议就是在XIB加载后,可以把XIB文件里面的东西转化为对象的关键所在。当每一个XIB文件载入后,就会开始解析文件里面的每个对象,并且对表示这个XIB文件的File's Owner(对故事板来说,就是每个界面的View Controller类)发送NSCoding协议里面的initWithCoder:信息,以通知File's Owner“现在我正要开始解析这个文件了”。因此,如果您的File's Owner是一个UIViewController,那么请不要在这个方法访问Outlet,因为在这个时候,这些Outlet还没有与类连接起来,也就是说您访问到的Outlet只是个nil。
接下来,XIB的加载器会准备将Outlet以及Action与类连接起来,并对XIB里面的对象发送awake-FromNib的信息。如果XIB里面包含了UIView,那么当UIView接收到awakeFromNib的信息之后,就会产生UIView的实例,并调用UIViewController的viewDidLoad方法。只有在这个方法调用之后,才可以访问界面控制器的Outlet。这就是为什么在前面几章的例子中,我们会尽量将访问Outlet的程序代码放在viewDidLoad里面。在viewDidLoad之后则依次发生viewWillAppear以及viewDidAppear事件。
现在问题来了,如果我们是通过程序代码的方式来产生界面控制器与界面的,那么UIView又是如何与UIViewController产生关联的呢?下面解答这个问题,我们可以看到在控制器里面有个view的属性,可以通过这个属性来设置这个控制器所管理的界面。如果是通过XIB的方式加载的,那么,XIB的加载器会把上面的流程所产生UIView的实例自动设置为UIViewController的view属性。但如果不是通过XIB的方式来加载控制器,那么您应该自己重载控制器的–(void) loadView方法,然后在这个方法中产生一个UIView的对象,并将其设置为这个控制器的view属性;否则,您在界面上将看不到任何东西。
6.1.4 常见接口模式
仔细观察一下在App Store上的应用程序,您应该会发现它们外观的布局似乎都有点雷同。其实这是因为Apple已经帮助开发人员预先定义好了几种界面呈现模式,且它们也可以一起搭配使用。这些模式包括。
导航栏接口模式的出现是为了应对界面间有上下页关系,上一页的标题会变成下一页的返回键(当然这点我们可以修改),如图6.1所示。例如,在设置界面上点选“通知”的话,界面会切换到通知的设置界面,同时在左上角也会出现一个“设置”的返回键。这个模式经常和表格控件一起使用,这是因为表格控件本身就具备了Master-Detail的特性。当然,这并不表示这两者必须一起使用,您仍然可以根据应用程序的情况来选择自己的界面呈现模式。
这个模式会在界面下方安排一列工具栏(UIToolbar),点选上面的按钮后,这些按钮会以高亮度来显示正在执行的功能。如图6.2所示,这里同时采用了导航栏模式与工具栏模式来呈现界面。
这个接口模式会在界面下方显示数个Tab,每个Tab都有其专用的界面(通常使用不同的界面控制器),用户可以很快地在这些界面中切换,就像切换频道一样方便,如图6.3所示。这个模式也常与导航栏接口模式一起使用。
模态接口模式会把要出现的界面由下方慢慢地拉到上方,并且遮盖住其他的界面;等到用户在界面上点选了某些按钮后,界面会被慢慢地拉到屏幕下方,如图6.4所示。
表格接口模式是最常见的一种数据呈现方式,用来表示Master-Detail的界面,也经常与其他模式合并使用,例如,联络人信息就是采用这种模式显示。我们会在后面以专门的一章来说明这个模式。