您的位置:首页 > 产品设计 > UI/UE

iPhone开发基础教程笔记(五)--第六章 多视图应用程序

2012-06-07 13:52 211 查看


第六章 多视图应用程序

6.1 View Switcher应用程序

本章将构建的View Switcher应用程序在外观上非常简单,但是从将要编写的代码上讲,他是目前为止我们碰到的最复杂的应用程序

。View Switcher由3个不同的控制器、3个nib文件和一个应用程序委托组成。

在首次启动时,View Switcher 在屏幕底部包含一个工具栏,工具栏中仅包含一个按钮Switch Views。视图的其余部分包括一个蓝

色背景和一个等待按下的按钮Press Me。

当按下Switch Views按钮时,背景将会变为黄色,等待按下的按钮的名称将会更改为Press Me,Too。

无论按下Press Me还是Press Me,Too,都会弹出一个警告,指示按下了哪个视图的按钮。

尽管可以编写一个单视图应用程序来实现相同的功能,但我们采用这种比较复杂的方法来演示多视图应用程序的机制。在这个简单

的应用程序中,实际上有3个视图控制器在交互,1个控制器控制蓝色视图,一个控制黄色视图,而第三个特殊的控制器用于在按下

Switch Views按钮时在这两个视图之间切换。

6.2 多视图应用程序的体系结构

在开始构建应用程序之前,我们先了解以下iPhone多视图应用程序的组成方式。

在这里,nib文件扮演着重要角色。创建项目之后,可以在Resources文件夹中找到MainWindow.xib。在该文件中,除了应用程序委

托和应用程序的主窗口之外,还有一个控制器类实例,他负责管理当前向用户显示哪个视图。这个根控制器通常是一个

UINavigationController或UITabBarController实例,但也可以是UIViewController的自定义子类。根控制器的任务是获取两个或

更多其他视图,并根据用户输入向用户提供适当的视图。例如,标签栏控制器会根据最后点击的标签栏项,在不同的视图和视图控

制器之间进行切换。当用户浏览分层数据时,导航控制器也具备相同的功能。

6.2.1 多视图控制器也是视图控制器

需要重点注意,每个多视图控制器都是一个视图控制器。甚至,已提供的多视图类UITabBarController和UINavigationController

都是UIViewController的子类,并且能够执行其他视图控制器能够执行的所有工作。根控制器是应用程序的主要视图控制器,也是

指定是否应该自动旋转到新方向的视图。

在多视图应用程序中,大部分屏幕都由一个内容视图组成,而每个内容视图都有自己的控制器以及输出口和动作。例如,在标签栏

应用程序中,触摸标签栏所生成的事件将会转到标签栏控制器中,但是屏幕上其他位置发送的事件将会转到与当前显示的内容视图

相对应的控制器中。

6.2.2 内容视图剖析

每个视图控制器都控制一个内容视图,应用程序的用户界面就是在这些内容视图中构建的。每个内容视图通常由2个或3个部分组成

:视图控制器、nib文件以及一个可选的UIView子类。使用多视图模版创建新的Xcode项目之后,始终可以看到所有这3个组件的文件

。控制器和nib文件通常是必须的。尽管可以不使用nib文件,而在代码中创建界面,但很少有人选择这种方法。耗时且难以维护。

每个内容视图还有一个UIView子类,但是并不是始终都需要它。实际上,一般情况都不需要UIView子类,但是当需要更改内容视图

的外观或行为,而使用Interface Builder中的属性检查器不能完成任务时,就需要使用这个子类。本章将为每个内容视图创建一个

nib和一个控制器类。

要间接创建内容视图,可以实例化他的控制器类,并指定一个nib名称。nib通常使用控制器类的名称来命名。因此,名为

MyController的类通常会加载一个名为MyController.xib的nib文件。

当应用程序家长MainWindow.xib时,也就创建了根控制器。

6.3 构建View Switcher

File=》New Project...或按下Cmd+Shift+N。选择Window-Based Application。输入项目名称:View Switcher

Window-Based Application模版实际上比我们以前使用的模版更加简单。这个模版仅提供一个窗口和一个应用程序委托,没有视图

,没有控制器。此模版很少用于创建应用程序,但是,通过从头开始创建应用程序,你将能很好地理解多视图应用程序的构造方式



展开Classes和Resources文件夹,看一下其中包含哪些内容。可以看到nib文件MainWindow.xib、Info.plist文件,以及类文件夹中

用于实现应用程序委托的两个文件。应用程序所需的其他所有内容都需要自己创建。

6.3.1 创建视图控制器和nib文件

对于从头创建多视图应用程序,一个麻烦就是必须创建若干个互相连接的对象。我们将创建组成应用程序的所有文件,然后才在IB

中进行操作以及编写代码。

首先创建所有文件,我们可以使用Xcode的代码感知功能更快地编写代码。如果某个类未声明,代码感知功能将无法使用,所以我们

每次都必须键入完整的类,这耗时且容易出错。

幸运的是,除了项目模版,Xcode还为许多标准文件类型提供了文件模版,这使得创建应用程序的基本框架非常简单。单击Classes

文件夹,然后File>NewFile或Cmd+N。看一下打开的窗口。

从左侧窗格选择Cocoa Touch Classes,可以看到大量关于常用的Cocoa Touch类的模版。选择UIViewController subclass,并单击

Next。输入名称SwitchViewController.m,确保选中了Also create "SwitchViewController.h"。然后单击Finish。Xcode应该向

Classes文件夹内添加两个文件,SwitchViewController类将充当根控制器。重复相同步骤两次,创建BlueViewController.m和

YellowViewController.m及其相应的头文件。这两个内容视图将由SwitchViewController进行切换。

我们还需要另外两个nib文件,分别对应刚才创建的两个内容视图。要创建nib文件,就要单击Groups&Files窗格中的Resources文件

夹,以便在正确的位置创建他们,然后再次按下Cmd+N或File=》New File...。出现帮助窗口之后,在左侧窗格中的iPhone OS标题

下的User Interface。

选择View XIB模版的图标,这叫创建一个带有内容视图的nib,然后单击Next按钮。当提示输入文件名时,输入BlueView.xib。重复

这些步骤,创建另一个nib文件YellowView.xib。

6.3.2 修改应用程序委托

多视图直通车的第一站是应用程序委托。单击G&Files窗格中的view_SwitchAppDelegate.h文件,并对文件进行以下更改:

#import <UIKit/UIKit.h>

//@class View_SwitcherViewController;将这一行修改为下一行

@class SwitchViewController;

@interface View_SwitcherAppDelegate:NSObject <UIApplicationDelegate> {

 IBOutlet UIWindow *window;

 IBOutlet SwitchViewController *switchViewController;

}

@property (nonatomic,retain) UIWindow *window;

@property (nonatomic,retain) SwitchViewController *switchViewController;

@end

IBOutlet SwitchViewController *switchViewController这个输出口必不可少,因为我们将编写代码来将根控制器的视图添加到应

用程序的主窗口。声明此输出口之后,当我们转到IB并在MainWindow.xib中添加一个SwitchViewController类实例之后,输出口将

自动创建。

现在,我们需要将根控制器的视图添加到应用程序的主窗口。单击View_SwitcherAppDelegate.m并添加以下代码:

#import "View_SwitcherAppDelegate.h"

#import "SwitchViewController.h"

@implementation View_SwitcherAppDelegate

@synthesize window;

@synthesize switchViewController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {

 [window addSubview:switchViewController.view];

 [window makeKeyAndVisible];

}

- (void) dealloc {

 [window release];

 [switchViewController release];

 [super dealloc];

}

@end

除了实现SwitchViewController输出口之外,我们还将根控制器的视图添加到了窗口中。记住,窗口是通向用户的唯一途径,因此

,需要向用户显示的任何内容都必须添加为窗口的子视图。

6.3.3 SwitchViewController.h

由于我们将在MainWindow.xib中添加一个SwitchViewController实例,因此现在是时候将所需的任何输出口或动作添加到

SwitchViewController.h头文件中了。

我们需要一个动作方法在两个视图之间进行切换。我们不需要任何输出口,但是需要另外两个指针,分别指向将要交换的两个视图

控制器。这些指针不需要输出口,因为我们将在代码中而不是在nib中创建他们。将以下代码添加到SwitchViewController.h:

#import <UIKit/UIKit.h>

@class BlueViewController;

@class YellowViewController;

@interface SwitchViewController:UIViewController {

 YellowViewController *yellowViewController;

 BlueViewController *blueViewController;

}

@property (retain,nonatomic) YellowViewController *yellowViewController;

@property (retain,nonatomic) BlueViewController *blueViewController;

- (IBAction)switchViews:(id)sender;

@end

6.3.4 修改MainWindow.xib

保存源代码,双击MainWindow.xib,在IB中打开它。nib的主窗口内应该有File's Owner、First Responder、

View_SwitcherAppDelegate以及Window 4个图标。我们需要添加另外一个图标,该图标表示根控制器的一个实例。因此IB的库不包

含SwitchViewController,所以必须添加一个视图控制器,并将其类改为SwitchViewController。

由于添加的类是UIViewController的一个子类,因此可以在View Controller中查看这些子类,将其中一个i额子类拖到nib的子窗口

中。

完成此操作之后,nib的主窗口将包含5个图标,以及一个包含灰色虚线圆角矩形的View窗口。

我们仅添加了一个UIViewController实例,但实际上我们需要一个SwitchViewController实例,因此,将视图控制器改为

SwitchViewController。在nib的主窗口中单击ViewController图标,并按下Cmd+4打开身份检查器。借助身份检查器,你可以指定

当前选定对象的类。单击标签为Class的组合框,也就是检查器顶部现在显示为UIViewController的组合框,将Class改为

SwitchViewController。完成更改之后,Class Actions部分将会显示switchViews:动作方法。还可以看到,在nib的主窗口中,新

图标的名称由ViewController变为了SwitchViewController。

现在,我们需要构建根控制器的视图。还记得将通用视图控制器拖到nib主窗口上时出现的新窗口吗?我们将在该窗口中构建

SwitchViewController的视图。

回忆一下,SwitchViewController的任务是在蓝色视图和黄色视图之间进行切换。为此,他需要为用户提供一种方式来更改视图,

所以我们将使用带有一个按钮的工具栏。现在让我们来构建这个工具栏。

从库中拖一个View出来。记住:这是带有灰色背景,名为View的窗口。应将灰色背景替换为这个新视图。

现在,从库中拖动一个工具栏到视图上,将其放在底部。该工具栏带有一个按钮。我们使用该按钮来让用户在不同的内容视图之间

切换。双击该按钮,将其标题更改为Switch Views。按下回车键提交更改。

现在,我们可以将工具栏按钮链接到动作方法。在此之前,应该注意:工具栏按钮与其他iPhone控件不同。他们仅支持一个目标动

作,并且仅符合条件时才触发该动作,相当于其他iPhone控件上的Touch Up Inside事件。

无需使用连接检查器将此按钮连接到我们的动作,只需单击Switch View按钮,等待片刻(避免双击),然后再次单击按钮将其选中

。通过查看检查器的标题栏并确保它显示为Bar Button Item,可以验证是否选定了按钮。

选定Switch Views按钮之后,按住Ctrl键并将该按钮拖到Switch View Controller图标,然后选择switchViews:动作。如果未弹出

switchView:动作,而是看到一个名为Delegate的输出口,这很可能是因为按住Ctrl键拖动的是工具栏而不是按钮。要解决此问题,

只需确保选定的是按钮而不是工具栏,然后重新按住Ctrl键进行拖动。

在前面,我们在View_SwitcherAppDelegate.h中创建了一个输出口,所以应用程序能够获得SwitchViewController实例并将其添加

到应用程序主窗口。现在,我们需要在nib中将SwitchViewController实例连接到该输出口。按住Ctrl键并将

View_SwitcherAppDelegate图标拖到SwitchViewController图标,然后选择SwitchViewController输出口。可能还会看到第二个输

出口,它具有一个类似的名称:viewController。如果确实看到该输出口,请确保连接到了SwitchViewController,而不是

ViewController。

至此任务就完成了,保存nib文件并返回Xcode,以实现SwitchViewController。

6.3.5 编写SwitchViewController.m

#import "SwitchViewController.h"

#import "BlueViewController.h"

#import "YellowViewController.h"

@implementation SwitchViewController

@synthesize blueViewController;

@synthesize yellowViewController;

- (void)viewDidLoad

{

  BlueViewController *blueController=[[BlueViewController alloc] initWithNibName:@"BlueView" bundle:nil];

 self.blueViewController=blueController;

 [self.view insertSubview:blueController.view atIndex:0];

 [blueController release];

}

- (IBAction)switchViews:(id)sender

{

 if (self.yellowViewController==nil)

 {

  YellowViewController *yellowController=[[YellowViewController alloc] initWithNibName:@"YellowView" bundle:nil];

  self.yellowViewController=yellowController;

  [yellowController release];

 }

 

 if (self.blueViewController.view.superview==nil)

 {

  [yellowViewController.view removeFromSupeview];

  [self.view insertSubview:blueViewController.view atIndex:0];

 }

 else 

 {

  [blueViewController.view removeFromSuperview];

  [self.view insertSubview:yellowViewController.view atIndex:0];

 }

}

- (void) dealloc {

 [yellowViewController release];

 [blueViewController release];

 [super dealloc];

}

...

@end

我们添加的第一个方法viewDidLoad是一个UIViewController方法,该方法将在载入nib时被调用。按住Option键并双击方法名称,

查看以下出现的文档。该方法在我们的超类中定义,当视图加载完成时,他将被覆盖为需要被通知的类。

我们将覆盖viewDidLoad以创建一个BlueViewController实例。我们使用initWithNibName方法从nib文件BlueView.xib家长

BlueViewController实例。注意,为initWithNibName提供的文件名未包含.xib扩展名。创建BlueViewController之后,我们将这个

新实例分配给blueViewController属性。

BlueViewController *blueController=[[BlueViewController alloc] initWithNibName:@"BlueView" bundle:nil];

 self.blueViewController=blueController;

接下来,我们插入蓝色视图做为根视图的一个子视图。将其插入在索引0的位置,这将告诉iPhone将此视图放在其他所有视图之后。

将该视图放在其他所有视图之后可以确保刚才在IB中创建的工具栏在屏幕上始终可见。

 [self.view insertSubview:blueController.view atIndex:0];

我们“延迟加载”了黄色视图,这有助于降低内存开销。黄色视图的实际加载在SwitchViews:方法中进行,让我们看一下该方法。

switchViews:方法首先检查yellowViewController是否为nil,如果是,则需要创建一个YellowViewController实例。

if (self.yellowViewController==nil)

 {

  YellowViewController *yellowController=[[YellowViewController alloc] initWithNibName:@"YellowView" bundle:nil];

  self.yellowViewController=yellowController;

  [yellowController release];

 }

接下来,看一下blueViewController的视图的超视图,以确定要显示哪个视图以及退出哪个视图。当前未显示视图的超视图将为nil

,这允许我们决定要显示和退出的视图。

 if (self.blueViewController.view.superview==nil)

 {

  [yellowViewController.view removeFromSupeview];

  [self.view insertSubview:blueViewController.view atIndex:0];

 }

 else 

 {

  [blueViewController.view removeFromSuperview];

  [self.view insertSubview:yellowViewController.view atIndex:0];

 }

6.3.6 实现内容视图

BlueViewController和YellowViewController实现的内容极其简单,只包含一个动作方法。这里以BlueViewController为例进行说

明,在BlueViewController.h中添加以下声明:

- (IBAction)blueButtonPressed:(id)sender;

打开BlueView.xib。首先,我们需要告诉BlueView.xib,将从磁盘加载此nib的类是BlueViewController,因此单击File's Owner图

标,并按下Cmd+4打开身份检查器。File's Owner默认为NSObject,请将其更改为BlueViewController。

接下来,在nib中更改视图的大小。双击View图标打开窗口,按Cmd+3打开大小检查器。将此视图的高度由480改为416。为什么是416

呢?因为要从480px减去状态栏的20px和工具栏的44px。按下Cmd+1打开属性检查器。单击标为Background的颜色,将此视图的背景

颜色改为蓝色。接下来,从库中拖出一个Round Rect Button到窗口上。双击该按钮,设置标题为Press Me。连接Touch Up Inside

事件为blueButtonPressed:动作方法。

我们还需要在此nib中做一件事情,那就是将BlueViewController's view输出口连接到nib中的视图。view输出口继承自父类

UIViewController,为控制器提供了访问他所控制的视图的能力。更改文件所有者的底层类时,会破坏现有的输出口连接。因此,

我们需要重新建立从控制器到其视图的连接。要重新建立连接,就要按住Ctrl键并将File's Owner图标拖到View图标,然后选择

view输出口。

几乎同样的操作对YellowView.xib做一遍。

BlueViewController.m中实现动作方法:

- (IBAction)blueButtonPressed:(id)sender

{ UIAlertView *alert=[[UIAlertView alloc]

  initWithTitle:@"Blue View Button Pressed"

  message:@"You pressed the button on the blue view"

  delegate:nil

  cancelButtonTitle:@"Yep,I did"

  otherButtonTitles:nil];

  [alert show];

  [alert release];

}

保存代码,现在可以运行我们的应用程序了。

但是 两个视图之间的转换过程比较生硬。那么,有没有使转换过程更加优美的方法呢?

6.4 制作转换动画

答案就是制作“转换动画”,为用户提供可视化的更改反馈。可以调用UIView中的几个类方法来指示应该制作转换动画,指示应该

使用的转换类型以及转换应该持续的时间。

回到SwitchViewController.m。将SwitchViews:方法替换为以下新版本:

- (IBAction)switchViews:(id)sender

{

 if (self.yellowViewController==nil)

 {

  YellowViewController *yellowController=[[YellowViewController alloc] initWithNibName:@"YellowView" bundle:nil];

  self.yellowViewController=yellowController;

  [yellowController release];

 }

 [UIView beginAnimations:@"View Flip" context:nil];

 [UIView setAnimationDuration:1.25];

 [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

 

 

 if (self.blueViewController.view.superview==nil)

 {

  [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight

     forView:self.view cache:YES];

  [blueViewController viewWillAppear:YES];

  [yellowViewController viewWillDisappear:YES];

  

  [yellowViewController.view removeFromSupeview];

  [self.view insertSubview:blueViewController.view atIndex:0];

  [yellowViewController viewDidDisappear:YES];

  [blueViewController viewDidAppear:YES];

 }

 else 

 {

  [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft

      forView:self.view cache:YES];

  [yellowViewController viewWillAppear:YES];

  [blueViewController viewWillDisappear:YES];     

  [blueViewController.view removeFromSuperview];

  [self.view insertSubview:yellowViewController.view atIndex:0];

  [blueViewController viewDidDisappear:YES];

  [yellowViewController viewDidAppeaer:YES];

 }

 [UIView commitAnimations];

}

编译这个版本并运行应用程序。新视图将会以翻页的形式显示,而不只是简单地出现。

这里也是先声明一个动画块,并指定动画的持续时间:

[UIView beginAnimations:@"View Flip" context:nil];

 [UIView setAnimationDuration:1.25];

然后设置动画曲线,这决定了动画的持续时间。默认情况下,动画曲线是一条线性直线,动画匀速地发生。我们在此处设置的选项

指示动画应该更改其速度,开始和结束时速度较慢,中间速度较快。这使动画看起来更加自然,不再那么呆板。

 [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

接下来,需要指定要使用的转换类型。在编写本书时,iPhone上提供了4种视图转换类型:

1)UIViewAnimationTransitionFlipFromLeft

2)UIViewAnimationTransitionFlipFromRight

3)UIViewAnimationTransitionFlipCurlUp

4)UIViewAnimationTransitionFlipCurlDown

缓存选项在动画开始时生成一个快照,并在动画的每个步骤中使用该图像,而不是重新绘制视图,这能够加快绘制的速度。应该始

终缓存动画,除非视图外观在动画期间需要改变。

[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight

     forView:self.view cache:YES];

设置转换类型之后,进行两次调用,调用在转换过程中使用的两个视图如下:

  [blueViewController viewWillAppear:YES];

  [yellowViewController viewWillDisappear:YES];

完成视图交换之后,在这两个视图上再进行两次调用:

  [yellowViewController viewDidDisappear:YES];

  [blueViewController viewDidAppear:YES];

UIView中这些方法的默认实现不会执行任何操作,所以我们对viewDidDisappear:和viewDidAppear:的调用也不会执行任何操作,因

为我们的视图是UIView的实例。

既然这些调用不执行任何操作,为什么还要调用他们呢?虽然我们现在使用的是UIView,但我们仍然可以在开发过程中的某一时刻

创建一个UIView子类,而且这个子类可能需要在转换前后执行某种操作。例如,当退出包含动画的视图时,这些视图往往会关闭这

些动画,而当显示这些视图时,他们又会打开这些动画。执行这4次调用不会影响到应用程序的性能,因为他们不会触发任何代码。

加入这些调用,我们就能够确保,即使在切换视图的类时,应用程序也能够继续工作。

当然,当指定了要制作成动画的所有更改之后,我们在UIView上调用commitAnimations:

[UIView commitAnimations];

6.5 重构

还有最后一个需要注意的地方:看一下刚才编写的新版本的switchViews:。具体看一下此部分:

if (self.blueViewController.view.superview==nil)

 {

  [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight

     forView:self.view cache:YES];

  [blueViewController viewWillAppear:YES];

  [yellowViewController viewWillDisappear:YES];

  

  [yellowViewController.view removeFromSupeview];

  [self.view insertSubview:blueViewController.view atIndex:0];

  [yellowViewController viewDidDisappear:YES];

  [blueViewController viewDidAppear:YES];

 }

 else 

 {

  [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft

      forView:self.view cache:YES];

  [yellowViewController viewWillAppear:YES];

  [blueViewController viewWillDisappear:YES];     

  [blueViewController.view removeFromSuperview];

  [self.view insertSubview:yellowViewController.view atIndex:0];

  [blueViewController viewDidDisappear:YES];

  [yellowViewController viewDidAppeaer:YES];

 }

只要看到两个或更多代码块非常类似,就应该检查代码,考虑一下是否能够将这些代码合并为一个代码块。调整代码以改善其质量

的流程,或者在不更改代码功能的前提下使其更具可维护性的流程称为“重构”!让我们重构这部分代码。

在大部分语言中,对于这种情形进行重构的方式应该是创建一个需要调用两次的新方法或函数。下面是在一个方法中处理这种情形

的另一种方式。看一下这个新的switchViews版本:

- (IBAction)switchViews:(id)sender

{

 if (self.yellowViewController==nil)

 {

  YellowViewController *yellowController=[[YellowViewController alloc] initWithNibName:@"YellowView" bundle:nil];

  self.yellowViewController=yellowController;

  [yellowController release];

 }

 [UIView beginAnimations:@"View Flip" context:nil];

 [UIView setAnimationDuration:1.25];

 [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

 

 UIViewController *coming=nil;

 UIViewController *going=nil;

 UIViewAnimationTransition transition; 

 if (self.blueViewController.view.superview==nil)

 { coming=blueViewController;

   going=yellowViewController;

   transition=UIViewAnimationTransitionFlipFromLeft;

 }

 else {

   coming=yellowViewController;

   going=blueViewController;

   transition=UIViewAnimationTransitionFlipFromRight;

 }

  [UIView setAnimationTransition:transition

     forView:self.view cache:YES];

  [coming viewWillAppear:YES];

  [going viewWillDisappear:YES];

  

  [going.view removeFromSupeview];

  [self.view insertSubview:coming.view atIndex:0];

  [going viewDidDisappear:YES];

  [coming viewDidAppear:YES]; 

 [UIView commitAnimations];

}

如果发现多次输入了非常相似的代码,或者需要复制或粘帖较大的代码块,那就应该停下工作,然后想想还有没有更好的方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐