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

9.7 第4个子控制器:可移动的行
9.7.1 编辑模式
移动并删除行,以及在表的指定位置插入行,所有这些任务都可以相当轻松地实现。可以通过使用表视图上的setEditing:animated:方法打开编辑模式(Editing Mode)来完成以上3个任务。此方法带有两个Boolean类型的参数,第一个参数指示编辑模式是打开还是关闭,第二个参数指示表是否进行动画转换。如果把编辑模式设置为当前状态,则第二个参数不起作用。
开启编辑模式后,大量新的委托方法就开始发挥作用。表视图使用他们询问某一行是否可以被移除或编辑,并告知用户是否真正移除或编辑了特定行。

9.7.2 创建一个新的二级控制器
命名为MoveMeController.m。
首先需要一个可变数组来存放数据和跟踪行变化,还需要一个操作方法在编辑模式的开启和关闭之间切换。此操作方法将由随后创建的导航栏按钮调用。
修改MoveMeController.h:

...{
 NSMutableArray *list;
}
@property ..
@end

实现MoveMeController.m:

- (IBAction)toggleMove {
 [self.tableView setEditing:!self.tableView.editing animated:YES];
}

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

 UIBarButtonItem *moveButton=[[UIBarButtonItem alloc] initWithTitle:@"Move"
   style:UIBarButtonItemStyleBordered
   target:self
   action:@selector(toggleMove)];
 self.navigationItem.rightBarButtonItem=moveButton;
 [moveButton release];
 [super viewDidLoad];
}

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
 return UITableViewCellEditingStyleNone;
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
 return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
 NSUInteger fromRow=[fromIndexPath row];
 NSUInteger toRow=[toIndexPath row];

 id object=[[list objectAtIndex:fromRow] retain];
 [list removeObjectAtIndex:fromRow];
 [list insertObject:object atIndex:toRow];
 [object release];
}
@end

让我们逐步地介绍。
toggleMove方法就是切换编辑模式。
viewDidLoad方法中创建了一个按钮栏项目,该按钮将被放置在导航栏上。将其标题设置为Move,并制定常量UIBarButtonItemStyleBordered表示需要一个简单的按钮。最后两个变量target和action,告诉按钮被单击时应该做什么。
现在,找到刚才添加的tableView:cellForRowAtIndexPath:方法。你注意到这一行新的代码了吗?
cell.showReorderControl=YES; 
(我并没有抄录tableView:cellForRowAtIndexPath:方法,因为该方法里除了上面这一行新代码没有其他新内容)
可以通过实现tableView:accessoryTypeForRowWithIndexPath:方法指定标准扩展图标。但是,重新排序控件不是标准扩展图标,他是一个特殊的例子,只在表处于编辑模式时才得到显示、要启动重新排序控件,必须在单元上设置一个属性。不过,需要注意的是,将此属性设置为YES不会真正显示重新排序控件,除非表进入编辑模式。
tableView:editingStyleForRowAtIndexPath:这个方法虽然短,但是很重要。在表视图中,我们希望能够对行进行重新排序,不过不希望用户能够删除或插入行。因此,我们实现了这个方法。通过这个方法,表视图可以询问指定行是否可以被删除,或是否可以将新行插入到指定位置。通过为每一行返回UITableViewEditingStyleNone,我们表示不支持插入或删除任何行。
下面是tableView:canMoveRowAtIndexPath:方法。每一行都将调用此方法,可以通过他禁止移动指定的行。如果某行的此方法返回NO,那么该行将不显示重新排序控件,用户不能够从当前位置移动该行。
最后一个方法tableView:moveRowAtIndexPath:fromIndexPath:是当用户移动一行时真正调用的方法。表视图已经移动了表中的行,用户应该看到了正确的结果,不过我们需要更新数据模型以保持两者同步,并避免显示问题的出现。

在RootViewController.m中添加此控制器。编译运行应用程序。


9.8 第5个子控制器:可删除的行
这一次我们加载属性列表文件,而不是通过硬编码来创建数组。你可以从项目归档文件09 Nav文件夹中获取名为computers.plist的文件,并将它添加到Resources文件夹。
添加DeleteMeController.m。更改DeleteMeController.h,添加一个NSMutableArray实例变量list;然后实现DeleteMeController.m:

...
- (IBAction)toggleEdit:(id)sender {
 [self.tableView setEditing:!self.tableView.editing animated:YES];
}

- (void)viewDidLoad {
 NSString *path=[[NSBundle mainBundle] pathForResource:@"computers" ofType:@"plist"];
 NSMutableArray *array=[[NSMutableArray alloc] initWithContentsOfFile:path];
 self.list=array;
 [array release];
 
 UIBarButtonItem *editButton=[[UIBarButtonItem alloc] initWithTitle:@"Delete"
   style:UIBarButtonItemStyleBordered
   target:self
   action:@selector(toggleEdit:)];
 self.navagationItem.rightBarButtonItem=editButton;
 [editButton release];
 [super viewDidLoad];
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
 NSUInteger row=[indexPath row];
 [self.list removeObjectAtIndex:row];
 [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAinimation:UITableViewRowAnimationFade];
}
@end

当用户完成一项编辑(删除或插入)操作时,表视图将调用tableView:commitEditingStyle:forRowAtIndexPath:方法。第一个参数指出在哪个表视图的行上进行编辑。第二个参数editingStyle是一个常量,他指示编辑的类型。目前,我们定义了3种编辑类型:其中一个是UITableViewCellEditingStyleNone,我们在最后一部分中使用他指示行不能被编辑。另外两个类型是UITableViewCellEditingStyleDelete(默认选项)和UITableViewCellEditingStyleInsert。UITableViewCellEditingStyleNone不会传递到此方法中,因为他用于表示这一行不允许被编辑。
本例忽略了此参数,因为行默认的编辑类型是删除类型,因此每一次调用此方法时,他都请求一项删除操作。可以使用此参数在一个表中同时允许插入和删除操作,不过在同一行中不允许。另一个编辑类型UITableViewCellEditingStyleInsert,通常用于需要让用户在列表中的一个指定位置插入行的情形。在顺序有系统来维护的列表中,比如一个按字母顺序排序的名称列表,用户通常会单击工具栏或导航栏来请求系统在详细视图中创建一个新的对象。只要用户指定好新的对象,系统就会把他放在合适的行。这里不再对插入的使用做介绍,不过插入的功能基本上和我们将要实现的删除操作相同。唯一的区别就是,需要创建一个新的对象并把他插入到指定位置,而不是从数据模型中删除指定的行。
最后一个参数IndexPath表示当前正在编辑哪一行。对于删除操作,此索引表示被删除的行。对于插入操作,他表示新行插入位置的索引。
此方法最后指定常量UITableViewRowAnimationFade动画类型,除了他,还有其他几个选项可以让行消失。可以查看Xcode文档浏览器中的UITableViewRowAnimation,看一下还有什么其他可用的动画。

同样,这DeleteMeController添加到RootViewController.m中。编译运行应用程序


9.9 第6个子控制器:可编辑的详细窗格
你在浏览iPhone上的多种应用程序时都会注意到,许多应用程序(包括Contacts应用程序)都把他们的详细视图实现为一个分组表。
现在让我们看看这是如何做到的。开始之前,需要显示一些数据,我们需要的不仅仅是一个字符串列表。在前两章中,当我们需要更复杂的数据时,都使用NSArray来保存一组已填充数据的NSDictionary实例。这种方式具有很大的灵活性,不过实现起来有些困难。而对于此表中的数据,我们将创建一个自定义Obj-C数据对象来存放将在列表中显示的单个实例。

9.9.1 创建数据模型对象
应用程序使用的属性列表包含有关美国总统的数据:每个总统的姓名、所属的政党、就职年份以及卸任年份。下面将创建存放这些数据的类。
单击Classes文件夹,Cmd+N打开新建文件向导。创建NSObject subClass,命名为President.m。修改President.h:
#define kPresidentNumberKey @"President"
#define kPresidentNameKey @"Name"
#define kPresidentFromKey @"FromYear"
#define kPresidentToKey @"ToYear"
#define kPresidentPartyKey @"Party"

#import <Foundation/Foundation.h>

@interface President:NSObject <NSCoding> {
 int number;
 NSString *name;
 NSString *fromYear;
 NSString *toYear;
 NSString *party;
}
@property ...
@end

从文件系统中读取字段时,这5个常量用于标识这些字段。通过此类遵循NSCoding协议,可以将此对象写入文件,或者从文件中读取它。
修改President.m:

...
@synthesize ...
- (void)dealloc {
 ...release...
}

#pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)coder
{ [coder encodeInt:self.number forKey:kPresidentNumberKey];
  [coder encodeObject:self.name forKey:kPresidentNameKey];
  ...
}

- (id)initWithCoder:(NSCoder *)coder
{
 if (self=[super init])
 {
  self.number=[coder decodeIntForKey:kPresidentNumberKey];
  ...
 }
 return self;
}
@end
通过遵循NSCoding协议,我们可以通过属性列表归档文件创建President对象。这个类中的其他内容应该不再需要解释了。
我们为你提供了一个属性列表文件,它包含所有美国总统的数据,可用于创建刚才编写的President对象的新实例。在项目归档文件的09 Nav文件夹中找到President.plist文件,并将它添加到项目的Resources文件夹中。

9.9.2 创建控制器
应用程序的这一部分需要两个新的控制器,一个用于显示被编辑的列表,另一个用于查看和编辑该列表中选中项目的详细信息。由于这两个视图控制器都以表为基础,因此不需要创建任何nib文件,不过我们需要两个独立的控制器类。
选中Classes文件夹,Cmd+N新建文件,创建UIViewController subClass,命名为PresidentsViewController.m和PresidentDetailController.m。

首先创建显示总统列表的视图控制器。修改PresidentsViewController.h,声明NSMutableArray *list;实例变量
实现PresidentsViewController.m,

...
- (void)viewDidLoad {
 NSString *path=[[NSBundle mainBundle] pathForResource:@"Presidents" ofType:@"plist"];
 NSData *data;
 NSKeyedUnarchiver *unarchiver;

 data=[[NSData alloc] initWithContentsOfFile:path];
 unarchiver=[[NSKeyedUnarchiver alloc] initForReadingWithData:data];
 
 NSMutableArray *array=[unarchiver decodeObjectForKey:@"Presidents"];//@"Presidents"键与创建此归档文件的值相同
 self.list=array;
 [unarchiver finishDecoding];
 [unarchiver release];
 [data release];
 
 [super viewDidLoad];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 NSUInteger row=[indexPath row];
 President *prez=[self.list objectAtIndex:row];

 PresidentDetailController *childController=[[PresidentDetailController alloc] initWithStyle:UITableViewStyleGrouped];

 childController.title=prez.name;
 childController.president=prez;

 NavAppDelegate *delegate=[[UIApplication sharedApplication] delegate];
 UINavigationController *navController=[delegate navController];
 [navController pushViewController:childController animated:YES];
 [childController release];
}
@end

9.9.3 创建详细视图控制器
下一个控制器有些复杂,不过我们会顺利地排除障碍。单击PresidentDetailController.h:

#define kNumberOfEditableRows 4
#define kNameRowIndex 0
#define kFromYearRowIndex 1
#define kToYearRowIndex 2
#define kPartyIndex 3

#define kLabelTag 4096

#import <UIKit/UIKit.h>
@class President;

@interface PresidentDetailController:UITableViewController <UITableViewDelegate,UITableViewDataSource,UITextFieldDelegate> {
 President *president;
 NSArray *fieldLabels;
 NSMutableDictionary *tempValues;
 UITextField *textFieldBeingEdited;
}
@property ...

- (IBAction)cancel:(id)sender;
- (IBAction)save:(id)sender;
- (IBAction)textFieldDone:(id)sender;
@end

究竟发生了什么事?这是全新的内容。在以前所有的表视图示例中,表中的每一行都对应数组中的一行。数组提供表所需要的所有数据。因此,例如,Pixar影片的表由一个字符串数组来控制,每个字符串都包含一个Pixar影片的标题。
前面的总统信息示例有两个不同的表,一个是总统的姓名列表,数组的每一行对应一个总统姓名。第二个表实现选中总统后显示的详细视图。由于这个表包含固定数量的字段,因此我们没有使用数组为此表提供数据,而是定义了一系列常量,我们将在表数据源方法中使用他们。这些常量定义了可编辑字段的数量,以及保存这些属性的行的索引值。
还有一个名称为kLabelTag的常量,我们将使用它从单元中检索UILabel,以便可以正确地设置行的标签。UITextField还应该有另一个标记吧?通常是这样。不过我们需要将文本字段的tag属性用于另一个目的。在设置文本字段时,我们必须使用另一个稍微方便一些的机制来获取文本字段。
此类遵循3个协议:表数据源和委托协议,以及一个新的协议UITextFieldDelegate。遵循这个新协议,当用户对某个文本字段作出更改时,我们会得到通知,以保存字段的值。此应用程序没有足够的行能让表上下滚动,不过在许多应用程序中,文本字段可以移出屏幕,而且可以被再次分配和再次使用。如果文本字段没有了,他存储的值也会相应地消失。因此用户作出更改时需要保存文本字段的值。
再往下,我们声明了一个President对象,他是我们将实际使用此视图编辑的对象。
第二个实例变量fieldLabels用于存放标签列表的数组。这些标签对应常量kNameRowIndex、kFromYearRowIndex、kToYearRowIndex和kPartyRowIndex。例如,kNameRowIndex被定义为0,那么显示总统姓名的那一行的标签在fieldLabels数组中被存储在索引0的位置。
可变字典tempValues用于存入用户更改后的字段值。我们不希望只见诶对president对象作出更改,因为如果用户选择了cancel按钮
再下面是一个i额UITextField的指针textFieldBeingEdited。指示当前正在被编辑的文本字段。
用户可以采用两种基本方法来结束对一个文本字段的编辑。第一种方法是,用户可以触摸另一个成为第一响应者的控件或文本字段。且委托方法textFieldDidEndEditing:被调用。
另一种方法是通过单击Save或Cancel按钮。这将调用save:或cancel:方法。在这两个方法中,必须是PresidentDetailController视图出栈,因为保存和取消动作都会结束当前的编辑会话。这就出现了一个问题。save:和action:操作方法很难找到刚才编辑的文本字段并保存数据。
textFieldDidEndEditing:确实可以访问文本字段。cancel:方法可以忽略textFieldDidEndEditing:,因为不需要保存更改,但是save:方法关心这些更改,他需要通过一种方法来保存他们。使用textFieldBeingEdited是个不错的主意。
单击PresidentDetailController.m:

#import ...
...
@synthesize ...

- (IBAction)cancel:(id)sender {
 NavAppdelegate *delegate=[[UIApplication sharedApplication] delegate];
 [delegate.navController popViewControllerAnimated:YES];
}

- (IBAction)save:(id)sender
{
 if (textFieldBeingEdited!=nil)
 {
  NSNumber *tagAsNum=[[NSNumber alloc] initWithInt:textFieldBeingEdited.tag];
  [tagValues setObject:textFieldBeingEdited.text forKey:tagAsNum];
  [tagAsNum release];
 }
 for (NSNumber *key in [tempValues allKeys])
 { switch ([key intValue]) {
    case kNameRowIndex:
      president.name=[tempValues objectForKey:key];
      break;
    case kFromYearRowIndex:
      president.fromYear=[tempValues objectForKey:key];
      break;
    case kToYearRowIndex:
      president.toYear=[tempValues objectForKey:key];
      break;
    case kPartyRowIndex:
      president.party=[tempValues objectForKey:key];
      break;
    default:
      break;
   }
 }
 NavAppdelegate *delegate=[[UIApplication sharedApplication] delegate];
 UINavigationController *navController=[delegate navController];
 [navController popViewControllerAnimated:YES];

 NSArray *allControllers=navController.viewControllers;
 UITableViewController *parent=[allControllers lastObject];
 [parent.tableView reloadData];
}
- (IBAction)textFieldDone:(id)sender
{ [sender resignFirstResponder];
}
- (void)viewDidLoad {
 NSArray *array=[[NSArray alloc]initWithObjects:@"Name:",@"From:",@"To:",@"Party:",nil];
 self.fieldLabels=array;
 [array release];

 UIBarButtonItem *cancelButton=[[UIBarButtonItem alloc] initWithTitle:@"Cancel"
   style:UIBarButtonItemStylePlain
   target:self
   action:@selector(cancel:)];
 self.navigationItem.leftBarButtonItem=cancelButton;
 [cancelButton release];

 UIBarButtonItem *saveButton=[[UIBarButton alloc] initWithTitle:@"Save"
    style:UIBarButtonItemStyleNone
    target:self
    action:@selector(save:)];
 self.navigationItem.rightBarButtonItem=saveButton;
 [saveButton release];

 NSMutableDictionary *dict=[[NSMutableDictionary alloc] init];
 self.tempValues=dict;
 [dict release];
 [super viewDidLoad];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 return kNumberOfEditableRows;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 static NSString *PresidentCellIdentifier=@"PresidentCellIdentifier";
 UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:PresidentCellIdentifier];
 if (cell==nil) {
  cell=[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:PresidentCellIdentifier] autorelease];
  UILabel *label=[[UILabel alloc] initWithFrame:CGRectMake(10,10,75,25)];
  label.textAlignment=UITextAlignmentRightl
  label.tag=kLabelTag;
  label.font=[UIFont boldSystemFontOfSize:14];
  [cell.contentView addSubview:label];
  [label release];

  UITextField *textField=[[UITextField alloc] initWithFrame:CGRectMake(90,12,200,25)];
  textField.clearsOnBeginEditing=NO;
  [textField setDelegate:self];
  textField.returnKeyType=UIReturnKeyDone;
  [textField addTarget:self
      action:@selector(textFieldDone:)
      forControlEvents:UIControlEventEditingDidEndOnExit];
  [cell.contentView addSubview:textField];
 }
 NSUInteger row=[indexPath row];
 
 UILabel *label=(UILabel *)[cell viewWithTag:kLabelTag]
 UITextField *textField=nil;
 for (UIView *oneView in cell.contentView.subViews)
 { if ([oneView isMemberOfClass:[UITextField class]])
     textField=(UITextField *)oneView;
 }
 label.text=[fieldLabels objectAtIndex:row];
 NSNumber *rowAsNum=[[NSNumber alloc] initWithInt:row];
 switch (row) {
  case kNameRowIndex:
    if ([[tempValues allKeys] containsObject:rowAsNum])
       textField.text=[tempValues objectForKey:rowAsNum];
    else
       textField.text=president.name;
    break;
  case kFromYearRowIndex:
    if ([[tempValues allKeys] containsObject:rowAsNum])
       textField.text=[tempValues objectForKey:rowAsNum];
    else
       textField.text=president.fromYear;
    break;
  case kToYearRowIndex:
    if ([[tempValues allKeys] containsObject:rowAsNum])
       textField.text=[tempValues objectForKey:rowAsNum];
    else
       textField.text=president.toYear;
    break;
  case kPartyRowIndex:
    if ([[tempValues allKeys] containsObject:rowAsNum])
       textField.text=[tempValues objectForKey:rowAsNum];
    else
       textField.text=president.party;
    break;
  default:
    break;
 }
 if (textFieldBeingEdited==textField)
   textFieldBeingEdited=nil;
 textField.tag=row;
 [rowAsNum release];
 return cell;
}

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 return nil;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{ self.textFieldBeingEdited=textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{ NSNumber *tagAsNum=[[NSNumber alloc] initWithInt:textField.tag];
  [tempValues setObject:textField.text forKey:tagAsNum];
  [tagAsNum release];
}
@end

在RootViewController.m中添加这个类实例,编译运行应用程序

9.10 更多内容
我们还需要添加一项内容。在刚才构建的版本中,键盘加入了一个Done按钮,单击此按钮会让键盘关闭。我们设想键盘应该拥有一个Return按钮而不是Done按钮,单击Return按钮时,用户将导航到下一行文本字段。
要完成上述过程,需要做的第一件事是用Return按钮代替Done按钮。
textField.returnKeyType=UIReturnKeyDone;//删除这一行即可,默认选项应该就是Return

然后在textFieldDone:方法中,不是简单地告知sender放弃第一响应者状态,而是通过某种方式指出下一个字段并让该字段称为第一响应者。

- (IBAction)textFieldDone:(id)sender
{ UITabelViewCell *cell=(UITableViewCell *)[[sender superview] superview];
  //sender的superview是表单元的内容视图,内容视图的superview是表单元
  UITableView *table=(UITableView *)[cell superview];
  //表单元的superview是封闭表视图
  NSIndexPath *textFieldIndexPath=[table indexPathForCell:cell]; 
  NSUInteger row=[textFieldIndexPath row];
  //获取该单元是哪一行
  row++;
  if (row >=kNumberOfEditableRows)
   row=0;
  NSUInteger newIndex[]={0,row};
  NSIndexPath *newPath=[[NSIndexPath alloc] initWithIndexes:newIndex length:2];
  UITableViewCell *nextCell=[self.tableView cellForRowAtIndexPath:newPath];
  //构建一个新的NSIndexPath,并使用该索引路径获取当前表示的下一个表单元的引用
  UITextField *nextField=nil;
  for (UIView *oneView in nextCell.contentView.subViews) {
   if ([oneView isMemberOfClass:[UITextField class]]) 
     nextField=(UITextField *)oneView;
  }
  //由于tag已经用作他途,因此我们需要遍历单元内容视图的子视图来查找文本字段。而不是使用tag来检索
  [nextField becomeFirstResponder];
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值