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