If you want to offer a single choice for a row tap, don‘t use an accessory icon if a row tap will only lead to a more detailed view of that row.
Mark the row with a disclosure indicator (gray arrow) if a row tap will lead to a new view (nota detail view).
If you want to offer two choices for a row, mark the row with a detail disclosure button.
This allows the user to tap on the row for a new view or the disclosurebutton for more details.
When we subclass UITableViewController, we inherit some nice functionality from that class that will create a table view with no need for a nib file.
@property (strong, nonatomic) BIDDisclosureDetailViewController *detailController; //保持的细节展示视图,为了复用
// Do any additional setup after loading the view.
NSString *detailMessage = [[NSString alloc] initWithFormat:@"This is details for %@.", selectedMovie];
[self.navigationController pushViewController: self.detailController animated:YES];
其实很简单,在获取cell的时候
if (self.selectedSnack == indexPath.row) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
然后在didSelectRowAtIndexPath委托中,记得去掉之前选中的状态
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row != self.selectedSnack) {
if (self.selectedSnack != NSNotFound) {
NSIndexPath *oldIndexPath = [NSIndexPath indexPathForRow:self.selectedSnack inSection:0];
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: oldIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath :indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.selectedSnack = indexPath.row;
}
//反选,去掉高亮,因为已经check mark了
[tableView deselectRowAtIndexPath: indexPath animated:YES];
}
第三个视图控制器BIDRowControlsViewController
cell.accessoryView = button;
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = self.characters[indexPath.row];
if (cell.accessoryView == nil) {
UIImage *buttonUpImage = [UIImage imageNamed:@"button_up.png"];
UIImage *buttonDownImage = [UIImage imageNamed:@"button_down.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setBackgroundImage:buttonUpImage
forState:UIControlStateNormal];
[button setBackgroundImage:buttonDownImage
forState:UIControlStateHighlighted];
[button setTitle:@"Tap" forState:UIControlStateNormal];
[button sizeToFit];
[button addTarget: self
action: @selector(tappedButton:)
forControlEvents: UIControlEventTouchUpInside];
cell.accessoryView = button;
}
//设置button tag,以备后用
cell.accessoryView.tag = indexPath.row;
return cell;
}
注意accessoryType和accessoryView
前者通过几个预定义类型,显示预定义视图,
后者可以设置为任何视图。
第四个视图控制器Movable Rows
editing mode
which is done using the setEditing:animated: method on the table view.
Once editing mode is turned on, a number of new delegate methods come into play.
@property (strong, nonatomic) NSMutableArray *words;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.title = @"Move Me";
self.rowImage = [UIImage imageNamed:@"moveMeIcon.png"];
self.words = [@[@"Eeny", @"Meeny", @"Miney", @"Moe", @"Catch", @"A",
@"Tiger", @"By", @"The", @"Toe"] mutableCopy];
self. navigationItem. rightBarButtonItem = self.editButtonItem;
}
return self;
}
self.navigationController
self.navigationItem.rightBarButtonItem
self.editButtonItem
All UIViewControllersub classes have an editButtonItem property that provide a default
bar item button for toggling its editing state. For a UITableViewController subclass like
BIDMoveMeViewController, this button will toggle the editing state of the UITableView using the
setEditing:animated: method。
所有的UIViewController子类,都有个editButtonItem,其用来翻转视图的editing state
UITableViewController 的此editButtonItem采用setEditing:animated: 方法,来翻转其tableView的editing state
注意,edit是指删除和插入新行
新的委托方法
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleNone;
}//注意,这里的editingStyle是指删除,或者插入新行,
默认为delete,所以只支持移动的话,需要实现此委托,返回None
- (BOOL)tableView:(UITableView *)tableView
shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
移动某行的时候,调用的是此委托方法
- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath
{
id object = [self.words objectAtIndex:fromIndexPath.row];
[self.words removeObjectAtIndex:fromIndexPath.row];
[self.words insertObject:object atIndex:toIndexPath.row];
}
Creating the Deletable Rows View
删除更简单
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath //删除时,为删除的行。插入时,为插入的行
{
[self.computers removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
不允许编辑(删除,插入),即只允许移动
UITableViewCellEditingStyleNone: We used this style in the
previous controller to indicate that a row can’t be edited. The option
UITableViewCellEditingStyleNone will never be passed into this method
because it is used to indicate that editing is not allowed for this row.
默认为删除
UITableViewCellEditingStyleDelete: This is the default option. We ignore this
parameter because the default editing style for rows is the delete style, so we
know that every time this method is called, it will be requesting a delete. You can
use this parameter to allow inserts and deletes within a single table.
UITableViewCellEditingStyleInsert: This is generally used when you need
to let the user insert rows at a specific spot in a list. In a list whose order is
maintained by the system, such as an alphabetical list of names, the user will
usually tap a toolbar or navigation bar button to ask the system to create a new
object in a detail view. Once the user is finished specifying the new object, the
system will place it in the appropriate row.
第6个控制器,可编辑detail
Sixth Subcontroller: An Editable Detail Pane
Creating the Data Model Object
@interface BIDPresident : NSObject <NSCoding, NSCopying>
@property (assign, nonatomic) NSInteger number;
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *fromYear;
@property (copy, nonatomic) NSString *toYear;
@property (copy, nonatomic) NSString *party;
@end
NSCoding协议
initWithCoder:
NSCopying协议
BIDPresidentsViewController subClass BIDSecondLevelViewController
BIDPresidentDetailViewController subClass UITableViewController 细节展示控制器
#import <UIKit/UIKit.h>
@class BIDPresident;
@protocol BIDPresidentDetailViewControllerDelegate;
@interface BIDPresidentDetailViewController : UITableViewController <UITextFieldDelegate>
@property (copy, nonatomic) BIDPresident *president; //data
@property (weak, nonatomic) id<BIDPresidentDetailViewControllerDelegate> delegate;//委托
@property (assign, nonatomic) NSInteger row; //选择的行
@property (strong, nonatomic) NSArray *fieldLabels;
- (IBAction)cancel:(id)sender;
- (IBAction)save:(id)sender;
- (IBAction)textFieldDone:(id)sender;
@end
//此协议,用来告诉其委托,数据已经修改,需要更新。
@protocol BIDPresidentDetailViewControllerDelegate <NSObject>
- (void)presidentDetailViewController:(BIDPresidentDetailViewController *)controller
didUpdatePresident:(BIDPresident *)president;
@end
NSString为何要用copy?而不是strong?
strong和retain同义, weak和assign同义, 为什么要采用这种说法, 似乎是ARC出现后为了消除引用计数的观念而采用的做法.
至于为什么要用copy, 由于纯NSString是只读的, 所以strong和copy的结果一样,据stackOverflow上的说法,是为了防止mutable string被无意中修改,
NSMutableString是NSString的子类, 因此NSString指针可以持有NSMutableString对象.
#define kNumberOfEditableRows 4
#define kNameRowIndex 0
#define kFromYearRowIndex 1
#define kToYearRowIndex 2
#define kPartyIndex 3
#define kLabelTag 2048
#define kTextFieldTag 4094
@implementation BIDPresidentDetailViewController {
NSString *initialText; //编辑前的初始值
BOOL hasChanges; //是否有修改
}
//表视图初始化
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle: UITableViewStyleGrouped]; //强制为分组模式
if (self) {
// Custom initialization
self.fieldLabels = @[@"Name:", @"From:", @"To:", @"Party:"];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemCancel 系统预定义按钮
target:self
action:@selector(cancel:)];
self.navigationItem.rightBarButtonItem =[[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemSave 系统预定义按钮
target:self
action:@selector(save:)];
}
return self;
}
//
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.allowsSelection = NO; //不允许选中
}
//取消
- (void)cancel:(id)sender
{
[self.navigationController popViewController Animated: YES]; //取消,则弹出
}
//保存
- (void)save:(id)sender
{
[self.view endEditing:YES];
if (hasChanges) {
[self.delegate presidentDetailViewController:self
didUpdatePresident:self.president]; //调用委托方法,让委托自己去更新数据
}
[self.navigationController popViewControllerAnimated:YES];
}
//完成输入,收缩键盘
//此方法为textField控件关联controller的Action,响应Did End On Exit事件
//注意其不是一个委托方法,仅仅是文本输入的事件响应
- (void)textFieldDone:(id)sender
{
[sender resignFirstResponder];
}
//
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return kNumberOfEditableRows;
}
//获取表视图单元
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 75, 25)];
label.tag = kLabelTag; //假如后面还需要获取该控件,则可以设置其tag
label.textAlignment = NSTextAlignmentRight;
label.font = [UIFont boldSystemFontOfSize:14];
[cell.contentView addSubview:label]; //直接在cell中添加子视图
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(90, 12, 200, 25)];
textField.tag = kTextFieldTag;
textField.clearsOnBeginEditing = NO;
textField.delegate = self; //设置委托
textField.returnKeyType = UIReturnKeyDone;
[textField addTarget:self
action:@selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit]; //输入完成,响应
[cell.contentView addSubview:textField];
}
UILabel *label = (id)[cell viewWithTag: kLabelTag];//通过Tag,再次获取label
label.text = self.fieldLabels[indexPath.row];
UITextField *textField = (id)[cell viewWithTag:kTextFieldTag]; //使用(id)做类型转换
textField.superview.tag = indexPath.row; //设置tag,后面会用到
switch (indexPath.row) {
case kNameRowIndex:
textField.text = self.president.name;
break;
case kFromYearRowIndex:
textField.text = self.president.fromYear;
break;
case kToYearRowIndex:
textField.text = self.president.toYear;
break;
case kPartyIndex:
textField.text = self.president.party;
break;
default:
break;
}
return cell;
}
//textField委托方法,开始编辑
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
initialText = textField.text;
}
//textField委托方法,完成编辑
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if (![textField.text isEqualToString:initialText]) {
hasChanges = YES;
switch (textField.superview.tag) {
case kNameRowIndex:
self.president.name = textField.text;
break;
case kFromYearRowIndex:
self.president.fromYear = textField.text;
break;
case kToYearRowIndex:
self.president.toYear = textField.text;
break;
case kPartyIndex:
self.president.party = textField.text;
break;
default:
break;
}
}
}
@end
实现BIDPresidentsViewController
@interface BIDPresidentsViewController : BIDSecondLevelViewController <BIDPresidentDetailViewControllerDelegate>
@property (strong, nonatomic) NSMutableArray *presidents;
@end
@implementation BIDPresidentsViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.title = @"Detail Edit";
self.rowImage = [UIImage imageNamed:@"detailEditIcon.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:@"Presidents"ofType:@"plist"];
NSData *data = [[NSData alloc] initWithContentsOfFile: path];
//archiver从文件获取对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData: data];
self.presidents = [unarchiver decodeObjectForKey:@"Presidents"];
[unarchiver finishDecoding];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.tableView registerClass:[UITableViewCell class]forCellReuseIdentifier:CellIdentifier];
}
#pragma mark - Table View Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.presidents count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
BIDPresident *president = self.presidents[indexPath.row];
cell.textLabel.text = president.name;
return cell;
}
#pragma mark - Table View Delegate Methods
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BIDPresident *president = self.presidents[indexPath.row];
BIDPresidentDetailViewController *controller = [[BIDPresidentDetailViewController alloc] init];
controller.president = president;
//注意BIDPresidentDetailViewController 的president属性为copy,
//意味着此赋值将创建一个独立的president,跟当前数组中的president不是同一份内存
//为什么必须为copy,因为在detailController中,textFiled编辑后,就会改写其维护的president对象,
//最终点取消则不会保存。
controller.delegate = self;
controller.row = indexPath.row;
[self.navigationController pushViewController:controller animated:YES];
}
//委托方法,需要更新president数据
#pragma mark - President Detail View Delegate Methods
- (void)presidentDetailViewController:(BIDPresidentDetailViewController *)controller
didUpdatePresident:(BIDPresident *)president
{
[self.presidents replaceObjectAtIndex: controller.row withObject:president];
[self.tableView reloadData];
}
@end
The keyboard should feature a Returnbutton instead of a Donebutton. When tapped, that button should take the user to the next row’s text field。
实现点击键盘return按钮,跳转到下一行。
- (void)textFieldDone: (id)sender
{
//[sender resignFirstResponder];
UITextField *senderField = sender;
NSInteger nextRow = (senderField.superview.tag + 1) % kNumberOfEditableRows;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:nextRow inSection:0];
UITableViewCell *nextCell = [self.tableView cellForRowAtIndexPath:indexPath];
UITextField *nextField = (id)[nextCell viewWithTag:kTextFieldTag];
[nextField becomeFirstResponder];
}