- iPhone UIKit详解
- 王志刚 王中元 朱蕾编著
- 3720字
- 2020-08-28 08:00:21
3.2 画面跳转
3.2.1 使用UITabBarController实现并列画面跳转
前一节介绍了由UIViewController实现的画面切换。实际上并非真的实现了两个画面间的跳转,而是同时启动两个画面,控制其中哪一个画面显示在前台,哪一个画面显示在后台而已。这种画面跳转方式有一个很大的缺点,即当画面数量增加时,画面跳转的实现代码将越来越复杂,而且各个画面间不可避免地有相互依赖关系。
实际上,在UIKit中提供了专用的管理画面跳转的类,这就是UITabBarController类以及UINavigationController类。本节首先介绍使用UITabBarController类如何实现画面间的跳转功能。
下面我们以上一节实例代码为基础,将其改造成使用UITabBarController类来实现画面间的跳转。首先将HelloWorldAppDelegate改名为MultiViewAppDelegate,并进行如下修改(代码中粗体字部分)。
[MultiViewAppDelegate.h] #import <UIKit/UIKit.h> @interface MultiViewAppDelegate :NSObject <UIApplicationDelegate> { UIWindow *window; UIViewController *rootController; } @property(nonatomic,retain)UIWindow *window; @end [MultiViewAppDelegate.m] #import "MultiViewAppDelegate.h" #import "ViewController1.h" #import "ViewController2.h" @implementation MultiViewAppDelegate @synthesize window; #pragma mark - #pragma mark Application lifecycle -(BOOL)application:(UIApplication *)application didFinishLaunchin gWithOptions:(NSDictionary *)launchOptions { // 初始化window实例变量 CGRect frame = [[UIScreen mainScreen] bounds]; self.window = [[UIWindow alloc] initWithFrame:frame]; // 创建母体Controller实例 rootController = [[UITabBarController alloc] init]; // 创建画面1与画面2的Controller实例 ViewController1 *tab1 = [[[ViewController1 alloc] init] autorelease]; ViewController2 *tab2 = [[[ViewController2 alloc] init] autorelease]; // 将画面1、画面2的Controller实例以数组的形式追加到母体Controller中 NSArray *tabs = [NSArray arrayWithObjects:tab1,tab2,nil]; [(UITabBarController)rootController setViewControllers:tabs animated:NO]; // 将母体Controller的view追加到Window中 [self.window addSubView:rootController.view]; [self.window makeKeyAndVisible]; return YES; } -(void)dealloc { [rootController release]; [window release]; [super dealloc]; } @end
要修改的地方并不多。首先删除前例中单独创建两个画面的实例变量ViewControllerl以及ViewController2,取而代之的是UITabBarController类型的实例变量rootController。上例中将两个画面的view直接追加到Window中,本例中只需要将UITabBarController类型实例变量的view追加到Window中。而将两个画面的UIViewController对象以数组的形式追加到UITabBarController类型的实例变量中,此时调用了setViewControllers:animated:方法,此方法的第一个参数即为UIViewController对象数组。
接着,我们开始修改两个画面的实例代码。其实施方法如下。
- 按钮去掉,删除与按钮相关的代码。
- 重写(或称覆盖)init方法,其中追加与标签相关的代码。
两个画面中的init方法的代码如下。
[ViewController1.m] -(id)init { if((self = [super init])){ // 设置tabBar的相关属性 self.title = @"Hello"; UIImage*icon = [UIImage imageNamed:@"ball1.png"]; self.tabBarItem = [[[UITabBarItem alloc] initWithTitle:@"Hello" image:icon tag:0] autorelease]; } return self; } [ViewController2.m] -(id)init { if((self = [super init])){ // 设置tabBar的相关属性 self.title = @"您好"; UIImage*icon = [UIImage imageNamed:@"ball2.png"]; self.tabBarItem = [[[UITabBarItem alloc] initWithTitle:@"您好" image:icon tag:0] autorelease]; } return self; }
重写的init方法中也没有追加多少内容。设置title属性后,导入图标用的图片,并将其设置到UIViewController的 tabBarItem属性中。调用initWithTitle:image:tag:方法进行UITabBarItem类的初始化。第一个参数为标签条中显示的标题;第二个参数为指定显示的图标图片;第三个参数为标签的序号,此序号通常用于程序内检索。
执行这个经过改造的工程后,将显示如图3-3所示的结果。
图3-3 标签实现的画面跳转
3.2.2 使用UINavigationController实现多层画面跳转
iPhone4手机的自带应用程序中,既有使用UITabBarController来进行画面切换控制的,也有使用UINavigationController来实现多画面间的跳转的。例如iPod音乐播放界面就采用了UITabBarController来进行画面切换控制,而iPhone手机设置程序则采用了UINavigationController来实现多层次画面间的跳转,图3-4、图3-5是这两个程序部分画面的跳转示意图。
图3-4 iPod音乐播放程序画面跳转示意图
与UITabBarController实现画面并行切换形式不同,UINavigationController是实现画面多层次跳转,也是其最大的特征。如图3-5所示,画面1-1可以跳转至其下一层的画面1-1-1以及画面1-1-2中。另外UINavigationController可以自动地记忆跳转所经过的路径,按照这些记录的路径信息,可以依次返回到上层画面中(即支持返回按钮)。
图3-5 iPhone设置程序部分画面跳转示意图
下面我们将上一节采用UITabBarController实现的画面切换程序,再一次改造成采用UINavigationController来实现画面的跳转程序。其中两个下一层的画面保持不变,在这之前我们还将追加一个主画面,主画面非常简单,只有一个iPhone表格视图组成,两个下层画面的名称依次显示在表格中,当单击任何一个名称时将会跳转到对应的下层画面中,整个应用程序的跳转示意图如图3-6所示。
图3-6 改造目标程序的跳转示意图
要实现上述程序,首先要进行如下两处修改。
● 在HelloWorldAppDelegate.m中将基准ViewController由UITableViewController替换为UINavigationController。
● 新追加主画面的TopMenuController类。
需要注意一点,这里我们对画面1以及画面2的实现代码其实是没有进行任何修改的。只是留下了些原实例中追加的关于UITabBarController的注释,请务必忽略。
下面我们看看HelloWorldAppDelegate.m的修改代码,见代码中的黑体字部分。
[HelloWorldAppDelegate.m] #import "HelloWorldAppDelegate.h" #import "TopMenuController.h" @implementation HelloWorldAppDelegate @synthesize window = window_; -(void)applicationDidFinishLaunching:(UIApplication *)application { // 初始化Window实例变量 CGRect bounds = [[UIScreen mainScreen] bounds]; window_ = [[UIWindow alloc] initWithFrame:bounds]; // 创建基准的Controller对象 TopMenuController*topMenu = [[[TopMenuController alloc] init] autorelease]; rootController_ = [[UINavigationController alloc] initWithRootVi ewController:topMenu]; // 将主画面的view追加到Window中 [window_ addSubview:rootController_.view]; [window_ makeKeyAndVisible]; } -(void)dealloc { [rootController_ release]; [window_ release]; [super dealloc]; } @end
大家可以看到,其实只修改了其中三行代码。首先需要导入 TopMenuController类,并创建其实例。接着初始化UINavigationController,需要向initWithRootViewController:方法中传入根画面的Controller,这里将创建好的TopMenuController实例传入。然后将UINavigationController的view属性追加到UIWindow中(使用addSubView:方法)。
下面是新追加的主菜单画面TopMenuController的代码。这里将TopMenuController以UITableViewController子类形式来创建。
[TopMenuController.h] #import <UIKit/UIKit.h> @interface TopMenuController :UITableViewController { @private NSMutableArray* items_; } @end [TopMenuController.m] #import "TopMenuController.h" @implementation TopMenuController -(void)dealloc { [items_ release]; [super dealloc]; } -(id)init { if((self = [super initWithStyle:UITableViewStylePlain])){ self.title = @"主菜单"; //-------------------------------<1> // 创建显示用数组 items_ = [[NSMutableArray alloc] initWithObjects: @"ViewController1", @"ViewController2", nil ]; } return self; } #pragma mark ----- UITableViewDataSource Methods ----- -(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { return [items_ count]; } -(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath { // 检查单元是否已经创建 UITableViewCell* cell = [tableView dequeueReusableCellWithIdenti fier:@"simple-cell"]; if(!cell){ // 没有创建的单元新创建 cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"simple-cell"] autorelease]; } // 设置单元中显示的文本字符串 cell.textLabel.text = [items_ objectAtIndex:indexPath.row]; //------<2> return cell; } #pragma mark ----- UITableViewDelegate Methods ---------<3> -(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath { Class class = NSClassFromString([items_ objectAtIndex:indexPath.row]); id viewController = [[[class alloc] init] autorelease]; if(viewController){[self.navigationController pushViewController:viewController animated:YES]; } } @end
与表相关的解说将放在第7章中,代码中有不理解的地方,请参考第7章的相关介绍。首先看看init方法中的内容。在titile属性中设置画面的标题(<1>)。使用UINavigationController时,此处设置的标题将在画面的最上方中心位置显示(见图3-7)。接着还创建了NSArray数组,NSArray数组中的元素将显示在表中。具体处理在tableView:cellForRowAtIndexPath:方法中,将NSArray中的元素设置到表的单元中。
图3-7 UINavigationController的标题
其次,我们再确认一下tableView:didSelectRowAtIndexPath:方法的处理内容。此方法将在表单元被触摸(单击)时调用,参数indexPath中保存了具体被触摸(单击)的行信息。
本例中,表单元中直接放置了跳转对象画面的类名,tableView:didSelectRow AtIndexPath:方法中首先创建被触摸行类的实例。然后跳转到对应画面中。调用UINavigationController的pushViewController:animated:方法实现画面跳转;向此方法的第一个参数中传入画面(UIViewController)的实例后,然后就会自动跳转到对应层次的画面中。另外如果将animated参数设置为YES,则下一画面将以动画的形式显示出来。此时UINavigationController实例可以通过UIViewController的navigationController属性获取。只要是UINavigationController管理下的UIViewController,随时都可以通过其navigationController属性获取UINavigationController实例本身。
这样就算完成了整个层次的画面跳转应用程序。跳转到下一层画面后,将自动显示如图3-8所示的返回按钮。
图3-8 UINavigationController的返回按钮
3.2.3 跳转到任意画面
上一小节中通过pushViewController:animated:方法能实现画面的跳转,而且能在导航条上自动追加返回上一画面的返回按钮。这种“返回到前一画面”的功能正确的表述应该为“返回到上一级”画面,调用popViewControllerAnimation:方法也能实现同样的功能。其他的如UINavigationController类还提供了直接返回到主画面的popToRootViewControllerAnimation:方法,以及返回到任意一级画面的popToViewController:animated:方法,以下是这三种方法的调用代码。
// 返回到上一级画面 [self.navigationController popViewControllerAnimation:Yes]; // 返回根画面 [self.navigationController popToRootViewControllerAnimation:Yes]; // 返回任意指定画面 [self.navigationController popToViewController:viewController animated:Yes];
另外,从iPhone OS 3.0以后,可以通过调用setViewController:animated:方法将画面的跳转历史路径(堆栈)完全替换。替换历史路径的示意图如图3-9所示。
图3-9 替换历史路径
实现图3-9所示的替换历史路径的代码如下。
// 将跳转历史路径替换为画面1> 画面1-3> 画面1-3-1 id scene1 = [[[Scene alloc] init] autorelease]; // 创建画面1的实例 id scene13 = [[[Scene3 alloc] init] autorelease]; // 创建画面1-3的实例 id scene131 = [[[Scene31 alloc] init] autorelease]; // 创建画面1-3-1的实例 NSArray *history = [NSArray arrayWithObjects:scene1,scene13,scene13 1,nil]; [self.navigationController setViewControllers:history animated:Yes];
popToViewController:animated:方法的第一个参数中必须传入UIViewController的实例,不是新创建的实例,而是实际跳转过程中原画面的实例。此时,可以通过UINavigationController的viewControllers属性来参照。viewControllers属性中保存的正是NSArray形式的跳转画面实例集合。
调用setViewControllers:animated方法进行跳转画面堆栈替换时,也可以viewControllers属性中保存的实例集合为基础,进行部分UIViewController实例的替换。
3.2.4 模态(modal)画面的显示方法
PC桌面软件中经常可以看到如“文件读取对话框”等模态对话框的画面类型。这些画面就显示在主画面的上方,当对话框中的操作结束,关闭对话框画面后将显示原来的画面,属于一种临时画面。iPhone应用程序中也能实现这种模态画面,例如iPhone通信录管理程序中,追加新的通信录时也使用了这种模态画面。
模态画面没有什么特别的地方,与其他画面一样也是由UIViewController的子类实现的画面,只是调用的方式不同而已。以下是模态画面显示的调用方式以及显示后关闭画面的实例代码。
// ModalDialog为UIViewController的子类 id dialog = [[[ModalDialog alloc] init] autorelease]; [self presentModalViewController:dialog animated:YES]; // 关闭模态UIViewController [self dismissModalViewControllerAnimationed:YES];
如上述代码所示,将UIViewController子类的实例作为presentModalViewController:animated:方法的第一个参数进行调用后,就能实现以模态方式显示画面。关闭时调用dismissModalViewControllerAnimationed:方法。模态画面调用后的示意图如图3-10所示。
图3-10 模态画面显示示意图
从iPhone OS 3.0开始,追加了设置模态画面显示/隐藏时动画效果的modalTranstionStyle属性,可设置三种不同的值,分别如下。
● UIModalTransitionStyleCoverVertical:画面从下向上徐徐弹出,关闭时向下隐藏(默认方式)。
● UIModalTransitionStyleFlipHorizontal:从前一个画面的后方,以水平旋转的方式显示后一画面。
● UIModalTransitionStyleCrossDissolve:前一画面逐渐消失的同时,后一画面逐渐显示。
以下是图3-10所示模态画面的代码,仅供参考。大家可以看到,其与普通的UIViewController子类没有任何区别。
// 以模态形式显示的画面 // 内容与普通画面一样 @interface ModalDialog :UIViewController @end @implementation ModalDialog -(void)viewDidLoad { [super viewDidLoad]; // 追加1个标签 UILabel* label = [[[UILabel alloc] initWithFrame:self.view.bounds] autorelease]; label.backgroundColor = [UIColor blackColor]; label.textColor = [UIColor whiteColor]; label.textAlignment = UITextAlignmentCenter; label.text = @"您好。我是模态画面。"; [self.view addSubview:label]; // 追加关闭按钮 UIButton* goodbyeButton = [UIButton buttonWithType:UIButtonTypeR oundedRect]; [goodbyeButton setTitle:@"Good-bye" forState:UIControlStateNormal]; [goodbyeButton sizeToFit]; CGPoint newPoint = self.view.center; newPoint.y += 80; goodbyeButton.center = newPoint; [goodbyeButton addTarget:self action:@selector(goodbyeDidPush) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:goodbyeButton]; } -(void)goodbyeDidPush { // 关闭模态对话框 [self dismissModalViewControllerAnimated:YES]; } @end