iPhone开发基础教程笔记(八)--第九章 导航控制器和表视图(一)

第九章 导航控制器和表视图

严格来说,导航控制器并不需要表视图。但是实际应用中,实现导航控制器时,几乎总是要实现至少一个表,并且通常是多个表,因为导航控制器的强大之处在于他能处理复杂的分层数据。在iPhone的小屏幕上,连续的表视图是表示分层数据最理想的方式。

9.1 导航控制器
UINavigationController是用于构建分层应用程序的主要工具,他在管理以及换入和换出多个内容视图方面与UITabBarController较为类似。两者之间的主要不同在于,UINavigationController是做为栈(stack)来实现的,这让他非常适合用于处理分层数据。

9.1.1 栈的性质
栈是一种常用的数据结构,采用后进先出的原则。
入栈=push 出栈=pop

9.1.2 控制器栈
导航控制器维护一个视图控制器栈。任何类型的属兔控制器都可以放入栈内。在设计导航控制器时,你需要指定用户看到的第一个视图。该视图是视图层次结构中最底层的视图,其控制器称为“根视图控制器”(root view controller)。根视图控制器是被导航控制器推入栈中的第一个视图控制器。当用户选择查看下一个视图时,栈中将加入一个新的视图控制器,他所控制的视图将展示给用户。我们把这些新的视图控制器称为子控制器(subcontroller)。本章的Nav应用程序由一个导航控制器和6个子控制器组成。
注意当前视图左上角的导航按钮。这个导航按钮类似于网页浏览器的后退按钮。当用户单击该按钮时,当前的视图控制器出栈,下一个视图称为当前视图。
通过这种设计模式,我们可以反复地构建复杂的分层应用程序。你不需要了解整个分层结构的复杂性。每个控制器只需要知道其子控制器,以便在用户作出选择时把适当的新的控制器对象加入到栈中。通过这种方式,你可以把若干个小部件组合成一个大型应用程序。

9.2 由6个部分组成的分层应用程序:Nav
应用程序运行后将显示一个选项列表。此顶级视图中的每一行分布表示一个不同的视图控制器,当选中其中一行时,对应的视图控制器被加入到控制器栈中。
每行右侧的灰色箭头是扩展图标(accessory icon)。这种特别的扩展图标被称为扩展指示器(Discloure indicator),用于告知用户触摸该箭头将切换到另一个表视图。
使用扩展指示器切换到包含选中行详细信息的视图是不合适的。此处,我们使用的是细节展示按钮(Detail Discloure Button),它显示应用程序6个子控制器中的第一个。细节展示按钮告诉你,选择该行将显示有关当前行的更多详细信息,并允许你对它们进行编辑。
与扩展指示器不同,细节展示按钮不仅仅是一个图标,他还是一个可单击的控件,因此一个给定的行可以有两个不同的选项。当用户选择该行时触发一个操作;当用户单击展示按钮时触发另一个操作。
再说一次,单击某行将显示该行的详细视图,但如果希望该行支持两个不同的选项,则不必使用扩展图标,而应使用细节展示按钮。如果希望单击某行时显示另一个完全不同的视图,且该视图并不是该行的详细视图,则应使用扩展指示器。
应用程序的第二个子控制器的视图可用于呈现“多选一”列表。此列表使用选中标记来标记当前选中的行。
第三个子控制器的视图在每一行的扩展视图中添加了一个开关控件。扩展视图位于表视图单元的最右侧,他通常用于存放扩展图标,但其用途远不止于此。
第四个子控制器 通过将表切换为编辑模式,用户可以对列表进行排序。
第五个子控制器 是展示编辑模式的另一种用法,即删除表中的行。
第六个子控制器 使用分组显示了一个可编辑的详细视图。详细视图这项技术在iPhone应用程序中得到了广泛应用。
原来有这么多工作要做。现在让我们开始吧!

9.3 构建Nav应用程序的骨架
Xcode为创建基于导航的应用程序提供了一个极好的模版,常用于创建分层应用程序。但是我们没有使用这个模版。我们将从零开始构建基于导航的应用程序。
在Xcode中,按下Cmd+shift+N,创建一个新项目,选择Window-Based Application。命名为Nav。单击Classes和Resources文件夹,可以看到此模版只提供一个应用程序委托,即MainWindow.xib。我们需要向MainWindow.xib中添加一个导航控制器,他将是应用程序的根控制器。由于所有的导航控制器必须拥有自己的根视图控制器,因此还需要创建他们。

9.3.1 创建根视图控制器
选择Classes文件夹,然后Cmd+N 创建新文件。选择Cocota Touch Classes和UIViewController subClass,命名为RootViewController.m。
现在,我们需要创建一个nib文件吗?
不!在本章中,我们将子类化UITableViewController而不是UIViewController。如果子类化UITableViewController,他将创建一个表视图,而不需要nib文件。如果需要创建多个表,使用这种方法显然是不可行的。不过,现在只需要一个表。

9.3.2 设置导航控制器
在编写代码之前,让我们了解一下组成应用程序的多个控制器的名称。
应用程序的根控制器处于最高级,他的视图将被添加到窗口中。本应用程序的根控制器是换入和换出组成视图层次的所有其他视图的导航控制器。
你可能有些不解。事实上,导航控制器拥有一个名称为rootViewController的属性,他是栈中最底层视图控制器的控制器。在我们的应用程序中,他就是最开始的分为6行的视图。问题在于根视图控制器有一个rootViewController属性。为了避免混淆,这里不再像以前一样将根控制器命名为rootController,而是将它命名为navController。
花一些时间理解上面的所有内容,并整理好自己的思路。现在,回到正题。
在NavAppDelegate.h中,添加以下代码:

#import <UIKit/UIKit.h>

@class NavViewController;

@interface NavAppDelegate:NSObject <UIApplicationDelegate> {
 IBOutlet UIWindow *window;
 IBOutlet UINavigationController *navController;
}
@property (nonatomic,retain) UIWindow *window;
@property (nonatomic,retain) UINavigationController *navController;
@end

其实现文件:
...
@synthesize navController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
 [window addSubciew:navController.view];
 [window makeKeyAndVisible];
}

...
[navController release];
..
@end

下一步,创建一个导航控制器,将它连接到刚才声明的navController输出口,然后告知导航控制器使用什么作为他的根视图控制器。
在IB中打开MainWindow.xib。从库中找到Navigation Controller,将其拖到nib主窗口,也就是MainWindow.xib的窗口,而不是标签为Window的窗口。
按下Ctrl键,从Nav App Delegate图标拖到新的Navigation Controller图标,选择navController输出口。如果有viewController输出口的话,小心不要选中它。
下一项任务有些棘手。我们需要告知导航控制器在哪儿能找到他的根视图控制器。最简单的方法是,使用窗口工具栏中间的View Mode按钮将nib的主窗口改为列表模式。
然后单击Navigation Controller左侧的小三角形,展开他,可以看到两个项目:Navigation Bar和View Controller(Navigation Item)。
单击View Controller(Navigation Item)图标,按下Cmd+4打开身份检查器。将基础类更改为RootViewController,然后按下返回键提交更改。按下Cmd+1切换到属性检查器。此处,如果愿意,我们还可以指定一个nib文件,从中加载根级视图。不过,我们将保留NIB Name字段为空,以此说明表视图控制器应该创建一个表视图实例。
当然,现在我们需要一个用于显示根视图的列表。第八章使用了简单的字符串数组。在此应用程序中,根视图控制器将管理即将构建的子控制器列表。单击任何行都会导致所选视图控制器的实例被添加到导航控制器的栈中。我们还希望能够在每一行旁边显示一个图标,因此创建一个UITableViewController子类(因为他的UIImage属性可以存放行图标),而不是将UIImage属性添加到创建的每个子控制器中。然后,我们将子类化这个新的类,而不是直接子类化UITableViewController,结果是所有的子类都拥有UIImage属性,这会让代码更加简洁明了。
我们不会真正创建这个新类的实例。他是单独存在的,这样便可以向接下来编写的其他控制器添加常规项目。在许多语言中,可以把他声明为一个抽象类,不过Obj-C不支持抽象类。我们可以创建不需要实例化的类,但编译器不会阻止我们采用与其他语言相同的方式创建这些类。与其他多数流行语言相比,Obj-C是一种比较宽松的语言,你可能不太习惯这一点。
在Xcode中单击Classes文件夹,然后按下Cmd+N打开新建文件向导。从左侧窗格中选择Cocoa Touch Classes,然后选择NSObject subClass。命名为SecondLevelViewController。创建后,打开SecondLevelViewController.h:改动:

#import <UIKit/UIKit.h>
@interface SecondLevelViewController:UITableViewController {
 UIImage *rowImage;
}
@property (nonatomic,retain) UIImage *rowImage;
@end

实现:
..
@synthesize rowImage;
...
@end

要作为二级控制器实现的任何控制器类都应该子类化SecondLevelViewController,而不是UITableViewController。

下面开始写代码吧。首先,在RootViewController.h中声明一个数组,并将父类改为UITableViewController:

...
@interface RootViewController:UITableViewController <UITableViewDelegate,UITableViewDataSource> {
 NSArray *controllers;
}
@property (nonatomic,retain) NSArray *controllers;
@end
这个数组存放二级视图控制器的实例,并为表提供数据。

在RootViewController.m中添加以下代码:

#import "RootViewController.h"
#import "SecondLevelViewController.h"
#import "NavAppDelegate.h"

@implementation RootViewController
@synthesize controllers;

- (void)viewDidLoad{
 self.title=@"Root Level";
 NSMutableArray *array=[[NSMutableArray alloc] init];
 self.controllers=array;
 [array release];
 [super viewDidLoad];
}

...
[controllers release];
...

#pragma mark -
#pragma mark Table Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 return [self.controllers count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 static NSString *RootViewControllerCell=@"RootViewControllerCell";
 UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:RootViewControllerCell];
 if (cell==nil) {
   cell=[[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:RootViewControllerCell] autorelease];
 }
 NSUInteger row=[indexPath row];
 SecondLevelViewController *controller=[controllers objectAtIndex:row];
 cell.text=controller.title;
 cell.image=controller.rowImage;
 return cell;
}


#pragma mark -
#pragma mark Table View Delegate Methods
- (UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
 return UITableViewCellAccessoryDisclosureIndicator;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 NSUInteger row=[indexPath row];
 SecondLevelViewController *nextController=[self.controllers objectAtIndex:row];
 
 NavAppDelegate *delegate=[[UIApplication sharedApplication] delegate];
 [delegate.navController pushViewController:nextController animated:YES];
}
@end

解析:
首先这里导入了新的SecondLevelViewController.h头文件。这样就可以在代码中使用SecondLevelViewController类,以便编译器知道rowImage属性。我们还导入了应用程序委托类的头文件,因为需要访问应用程序委托。应用程序委托拥有一个指向应用程序导航控制器的输出口,因此无论何时需要访问导航控制器,都可以通过应用程序委托来完成。
viewDidLoad方法中,我们设置了title。通过询问当前活动控制器的标题,导航控制器知道要在导航栏的标题中显示什么。因此,为基于导航应用程序的所有控制器实例设置标题很重要,因为这能让用户知道自己处于哪个阶段。
然后创建一个可变数组,并把它分配给controllers属性。
viewDidLoad方法最后是[super viewDidLoad],当我们覆盖父方法时,这是个好习惯。
最后,tableView:didSelectRowAtIndexPath:方法中,我们获得了应用程序委托,并使用他的输出口navController将获得的下一个控制器放入到导航控制器栈中。
NavAppDelegate *delegate=[[UIApplication sharedApplication] delegate];
 [delegate.navController pushViewController:nextController animated:YES];

至此,应用程序的骨架已经完成了。你需要将Core Graphics骨架连接到项目中。
现在可以保存运行应用程序看一下了。不过,由于当前数组是空的,所以根视图还没有任何行。

现在我们已经准备好开发二级视图了。在此之前,从09 Nav文件夹中取出图像图标。名称为Image的子文件夹包含6个.png图像,作为行图像。把他们加入到Resources文件夹。

9.4 第一个子控制器:展示按钮视图
现在,实现第一个二级视图控制器。首先需要创建一个SecondLevelViewController子类。
选择Classes文件夹,Cmd+N。左侧窗格选择Cocoa Touch Classes,从右上窗格选择UIViewController subClass。命名为DisclosureButtonController.m。
在顶层视图中单击DisclosureButtons时,此类用于管理将显示的影片表。
当用户单击任意影片标题时,应用程序将展开另一个视图,这个视图会报告选中了哪一行。因此,我们还需要创建一个可展开的详细视图,重复上面的步骤创建另一个文件,将它命名为DisclosureDetailController.m。
详细视图是一个非常简单的视图,我们只能在这个视图中设置一个标签。她是不可编辑的,我们使用它展示如何将值传递到子控制器中。因此这个控制器不对表视图负责,所以还要为控制类创建一个nib文件。在创建nib之前,先为标签添加一个输出口。在DisclosureDetailController.h中添加以下代码:

... {
 IBOutet UILabel *label;
 NSString *message;
}
...
@end

为什么要同时添加一个标签和一个字符串呢?还记得延迟加载吗(Lazy Loading)?没错,视图控制器将应用延迟加载。当我们创建自己的控制器时,他不会加载nib文件,直到nib文件被实际显示。当控制器被加入到导航控制器的栈中时,我们不能指望拥有要设置的label。如果未加载nib文件,则label只是指向nib的一个指针。不过没关系,我们会将message设置为需要的值。并且,在viewWillAppear:方法中,将根据message中的值设置标签。
为什么此处要使用viewWillAppear:进行更新,而不是像以前一样使用iewDidLoad方法呢?问题是viewDidLoad只在第一次加载其视图的时候才得到调用。而在此处,我们重新使用了DisclosureDetailController的视图。不管怎样,当你单击展示按钮时,详细信息就出现在相同的DisclosureDetailController视图中。

在DisclosureDetailController.m中添加以下代码:
#import "DisclosureDetailController.h"
@implementation DisclosureDetailController
@synthesize label;
@synthesize message;
- (void)viewWillAppear:(BOOL)animated {
 label.text=message;
 [super viewWillAppear:animated];
}

这很简单,是吗?很好,下面为此文件创建nib。
选择Resources文件夹,然后Cmd+N创建新文件。左侧窗格选User Interfaces,右上窗格选View XIB。命名为DisclosureDetail.xib。
首先设置xib。单击File's Owner,Cmd+4打开身份检查器。将基础类改为DisclosureDetailController。现在,按下Control键并从File's Owner图标拖到View图标,选择view输出口重建控制器与视图之间的连接,因此这个连接在更改控制器类的时候中断了。
从库中拖出Label,调整大小,位置,对齐。设置输出口等。

在本例中,列表只显示来自数组的多个行,因此我们需要声明一个名称为list的NSArray。还需要声明一个实例变量,用来存放子控制器的一个实例,他指向刚才创建的DisclosureDetailController类的一个实例。用户每次单击详细展示按钮时,我们都会为该控制器类分配一个新实例。不过创建一个实例后重复使用的效率会更高。下面对DisclosureButtonController.h做如下更改:

#import "SecondLevelViewController"
@class DisclosureDetailController

@Interface DisclosureButtonController:SecondLevelViewController <UITableViewDelegate,UITableViewDataSource> {
 NSArray *list;
 DisclosureDetController *childController;
}
@property (nonatomic,retain) NSArray *list;
@end

注意,此处并没有为childController声明属性。我们在类内部使用此实例变量,不希望把他呈献给其他人,因此不会声明属性来宣扬他的存在。

实现部分:DisclosureButtonController.m:
#import "NavAppDelegate.h"
#import "DisclosureDetailController.h"

...
- (void)viewDidLoad {
 NSArray *array=[[NSArray alloc] initWithObjects:@"Toy Story",@"A Bug's Life",...,@"up",nil];
 self.list=array;
 [array release];
 [super viewDidLoad];
}

#pragma mark -
#pragma mark Table DataSourceMethods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 return [list count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 ...
 NSString *rowString=[list objectAtIndex:row];
 cell.text=rowString;
 return cell;
}

#pragma mark -
#pragma mark Table DelegateMethods
- (UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSINdexPath *)indexPath {
 return UITableViewCellAccessoryDetailDisclosureButton;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"Hey,do you sure the disclosure button?"
  message:@"if you are trying to drill down,touch that instead"
  delegate:nil
  cancelButtonTitle:@"Won't happen again"
  otherButtonTitles:nil];
 [alert show];
 [alert release];
}

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
 if (childController==nil)
   childController=[[DisclosureDetailController alloc] initWithNibName:@"DisclosureDetail" bundle:nil];
 childController.title=@"Disclosure Button Pressed";
 NSUInteger row=[indexPath row];
 
 NSString *selectedMovie=[list objectAtIndex:row];
 NSString *detailMessage=[[NSString alloc] initWithFormat:@"You pressed the disclosure button for %@",selectedMovie];
 childController.message=detailMessage;
 childController.title=selectedMovie;
 [detailMessage release];
 NavAppDelegate *delegate=[[UIApplication sharedApplication] delegate];
 [delegatge.navController pushViewController:childController animated:YES];
}
@end

解析:3个新的委托方法
tableView:accessoryTypeForRowWithIndexPath: 返回UITableViewCellAccessoryDetailDisclosureButton表示每一行都显示一个展示按钮。
。。。

现在二级控制器已经完成了,她是我们的细节控制器。接下来的任务就是创建一个二级控制器实例,并将他添加到RootViewController的控制器中。单击RootViewController.m。在viewDidLoad方法中添加以下代码:

- (void)viewDidLoad {
 self.title=@"Root Level";
 NSMutableArray *array=[[NSMutableArray alloc] init];

 //Disclosure Button
 DisclosureButtonController *disclosureButtonController=[[DisclosureButtonController alloc] initWithStyle:UITableViewStylePlain];
 disclosureButtonController.title=@"Disclosure Buttons";
 disclosureButtonController.rowImage=[UIImage imageNamed:@"disclosureButtonControllerIcon.png"];
 [array addObject:disclosureButtonController];
 [disclosureButtonController release];

 self.controllers=array;
 [array release];
 [super viewDidLoad];
}

上面的
DisclosureButtonController *disclosureButtonController=[[DisclosureButtonController alloc] initWithStyle:UITableViewStylePlain];
UITableViewStylePlain表示我们需要一个索引表而不是分组表。

现在保存编译运行应用程序。看看效果
触摸细节展示按钮会展开另一个视图。新视图将显示我们传递给他的信息。虽然这是一个简单的例子,但任何时候需要显示详细视图时都应使用这个技巧。


9.5 第二个子控制器:校验表
我们将要实现的下一个二级视图也是一个表视图,不过这一次将使用扩展图标,以允许用户能且仅能从列表中选择一个项目。我们将使用扩展图标在当前选中行的旁边设置一个选中标记,而且当用户单击另一行时,将更改选项。
由于这个视图是一个表视图,他没有任何详细视图,因此不需要创建新nib,不过我们确实需要创建另一个SecondLevelViewController子类。选择Classes文件夹,Cmd+N新建文件,现在Cocoa Touch Classes,UIViewController subclass图标,命名为CheckListController.m。
除了更改超类并遵循两个协议之外,我们还需要通过一个方法来跟踪当前所选中的行。下面将声明一个NSIndexPath属性来跟踪最后选中的行。单击CheckListController.h,添加以下代码:

... {
 NSArray *list;
 NSIndexPath *lastIndexPath;
}
@property ..
@end

切换到CheckListController.m:

...
@synthesize ..
...
- (void)viewDidLoad {
 NSArray *array=[[NSArray alloc] initWithObjects:@"Who Hash",@"Bubba Gump Shrimp Etouffee",...,@"Blancmage",nil];
 self.list=array;
 [array release];
 [super viewDidLoad];
}
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 ...
 NSUInteger row=[indexPath row];
 NSUInteger oldRow=[lastIndexPath row];
 cell.text=[list objectAtIndex:row];
 cell.accessoryType=(row==oldRow && lastIndexPath !=nil)?UITableViewCellAccessoryCheckmark:UITableViewCellAccessoryNone);
 return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 int newRow=[indexPath row];
 int oldRow=[lastIndexPath row];
 if (newRow!=oldRow)
 {
  UITableViewCell *newCell=[tableView cellForRowAtIndexPath:indexPath];
  newCell.accessoryType=UITableViewCellAccessoryCheckmark;

  UITableViewCell *oldCell=[tableView cellForRowAtIndexPath:lastIndexPath];
  oldCell.accessoryType=UITableViewCellAccessoryNone;
  lastIndexPath=indexPath;
 }
 [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@end


首先看一下tableView:cellForRowAtIndexPath:方法.首先我们从这个单元和当前选项中提取行,并分配标题: 

 NSUInteger row=[indexPath row];
 NSUInteger oldRow=[lastIndexPath row]; 
cell.text=[list objectAtIndex:row];
然后根据两行是否相同,将扩展图标设置为显示检验标记或者不显示任何东西。换句话说,如果某行的表正在请求单元,而这行正好是当前选中的行,我们就将扩展图标设置为一个选中标记;否则,将它设置为不显示任何东西。注意,还需要检查lastIndexPath来确保他不为nil。这样做是因为值为nil的lastIndexPath不表示任何选项。但是,在nil对象上调用row方法将返回0,他是一个有效行,不过我们不希望在0行上放置一个检验标记,因为实际上没有任何选项。
 cell.accessoryType=(row==oldRow && lastIndexPath !=nil)?UITableViewCellAccessoryCheckmark:UITableViewCellAccessoryNone);

现在跳转到最后一个方法。你之前看到过tableView:didSelectRowAtIndexPath:方法,不过这里有些新内容。
我们获取了当前选中的行和上一次选中的行
 int newRow=[indexPath row];
 int oldRow=[lastIndexPath row];
这样做是因为如果新行和旧行相同,就不做任何更改:
 if (newRow!=oldRow)
 {
下一步,获取刚才选中的单元,并指定一个检验标记做为他的扩展图标:
  UITableViewCell *newCell=[tableView cellForRowAtIndexPath:indexPath];
  newCell.accessoryType=UITableViewCellAccessoryCheckmark;
然后,获取上一次选中的单元,将它的扩展图标设置为无。
  UITableViewCell *oldCell=[tableView cellForRowAtIndexPath:lastIndexPath];
  oldCell.accessoryType=UITableViewCellAccessoryNone;
然后更新lastIndexPath
  lastIndexPath=indexPath;
 }
完成之后,告诉表视图取消选中刚才选中的行,因为我们不希望该行一直保持突出显示。我们已经用选中标记标记了该行,把他保留为蓝色将是很麻烦的一件事。
 [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

接下来,只需要添加此控制器的一个实例到RootViewController的controller数组。

...
//Check List
CheckListController *checkListController=[[ CheckListController alloc] initWithStyle:UITableViewStylePlain];
checkListController.title=@"Check one";
checkListController.rowImage=[UIImage imageNamed:@"checkmarkControllerIcon.png"];
[array addObject:checkListController];
[checkListController release];
...


9.6 第三个子控制器:表行上的控件
第八章展示了如何向表视图添加子视图以自定义其外观,但是没有在内容视图中放置除标签之外的任何活动控件。这一次,我们试着在表视图单元中添加控件。在本例中,我们将向每一行添加一个开关,不过对大多数控件的操作方法基本相同。这一次将向扩展窗格添加控件,这就意味着当单击扩展窗格时,用户将更改开关的值。另外,在其他任何地方单击一行都将弹出一个警告,告知我们此行开关的状态是开还是关。此技巧将向你展示如何检索在表视图单元上使用控件的值。

选择Classes文件夹,Cmd+N,新建文件,选择Cocoa Touch Classes,UIViewController subClass。命名为RowControllsController.m。
修改RowControllsController.h:

...
#define kSwitchTag 100
@interface RowControllsController:SecondLevelViewController <UITableViewDelegate,UITableViewDataSource> {
 NSArray *list;
}
@property ..
@end

RowControllsController.m:

...
- (void)viewDidLoad {
 NSArray *array=[[NSArray alloc] initWithObjects:@"Who Hash",@"Bubba Gump Shrimp Etouffee",...,@"Blancmage",nil];
 self.list=array;
 [array release];
 [super viewDidLoad];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 NSUInteger row=[indexPath row];
 UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
 UISwitch *switchView=(UISwitch *)[cell viewWithTag:kSwitchTag];
 //由于将这个值分配给了行的扩展视图,在这里还以通过如下调用获得开关视图:
 //UISwitch *switchView=(UISwitch *)cell.accessoryView;
 //为什么不这么做呢?实际上,用上面的方式获取开关视图没什么问题,只不过使用标记稍微安全一些。在后面的实例中,如果其他一些代码段为扩展视图分配了其他一些对象,则会造成在错误类型的对象上进行调用。这些bug很难调试,甚至会导致应用程序崩溃。另一方面,如果某个人用一个新的视图替换了扩展视图,viewWithTag:方法将返回nil。在Obj-C中,向nil传递消息是允许的,而且一般不会导致应用程序崩溃,虽然无任何意义,但的确是很好的精神食粮。在现实中,这两行代码之间区别可能更多地取决于个人喜好。

 NSString *baseString=@"%@ %@.";
 NSString *onString=(switchView.on)?@"IS on":@"IS NOT on";
 NSString &robot=[list objectAtIndex:row];
 NSString &messageString=[[NSString *alloc]initWithFormat:baseString,robot,onString];
 UIAlertView *alert=...;
 [alert show];
 [alert release];
 [messageString release];
}
@end


现在将此控制器添加到RootViewController的数组中
省略

保存编译运行程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值