编辑表格
添加编辑功能之后,表格就会变得丰富起来。编辑功能可以令表格里的静态信息变成能够滚动的互动式控件,从而使用户可以添加或者移除数据。虽说处理编辑功能所要的代码有些复杂,但同样的技术却可以反复运用在各种应用的程序中。我们只要掌握了“进入编辑状态”、“离开编辑状态”,以及“实现撤销功能”等基础知识,就能把他们移用到其他程序上面。
主要代码如下:
@implementation ViewController
{
NSMutableArray *items;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor whiteColor];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
self.tableView.rowHeight = 20;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.navigationItem.rightBarButtonItem = self.editButtonItem;
items = [NSMutableArray array];
for (int i=0; i<20; i++) {
[items addObject:[NSString stringWithFormat:@"%d",i]];
}
[UIApplication sharedApplication].applicationSupportsShakeToEdit = YES;
[self setBarButtonItems];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.textLabel.text = items[indexPath.row];
return cell;
}
- (void)setBarButtonItems
{
if (self.undoManager.isUndoing || self.undoManager.isRedoing) {
[self performSelector:@selector(setBarButtonItems) withObject:nil afterDelay:0.1f];
return;
}
UIBarButtonItem *undo = SYSBARBUTTON_TARGET(UIBarButtonSystemItemUndo, self.undoManager, @selector(undo));
undo.enabled = self.undoManager.canUndo;
UIBarButtonItem *redo = SYSBARBUTTON_TARGET(UIBarButtonSystemItemRedo, self.undoManager, @selector(redo));
redo.enabled = self.undoManager.canRedo;
UIBarButtonItem *add = SYSBARBUTTON_TARGET(UIBarButtonSystemItemAdd, self, @selector(addItem:));
self.navigationItem.leftBarButtonItems = @[add,undo,redo];
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated{
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
if (path) {
[self.tableView deselectRowAtIndexPath:path animated:YES];
}
[self setBarButtonItems];
}
- (void)updateItemAtIndexPath:(NSIndexPath *)indexPath withObject:(id)object
{
id undoObject = object ? nil : items[indexPath.row];
[[self.undoManager prepareWithInvocationTarget:self] updateItemAtIndexPath:indexPath withObject:undoObject];
[self.tableView beginUpdates];
if (!object) {
[items removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
}else{
[items insertObject:object atIndex:indexPath.row];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
[self.tableView endUpdates];
[self performSelector:@selector(setBarButtonItems) withObject:nil afterDelay:0.1f];
}
- (void)addItem:(id)sender
{
NSIndexPath *newPath = [NSIndexPath indexPathForRow:items.count inSection:0];
NSInteger count = items.count;
count ++;
[self updateItemAtIndexPath:newPath withObject:[NSString stringWithFormat:@"%ld",count]];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
[self updateItemAtIndexPath:indexPath withObject:nil];
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
if (sourceIndexPath.row == destinationIndexPath.row) {
return;
}
[[self.undoManager prepareWithInvocationTarget:self] tableView:self.tableView moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
id item = [items objectAtIndex:sourceIndexPath.row];
[items removeObjectAtIndex:sourceIndexPath.row];
[items insertObject:item atIndex:destinationIndexPath.row];
if (self.undoManager.isUndoing || self.undoManager.isRedoing) {
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:@[sourceIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView insertRowsAtIndexPaths:@[destinationIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView endUpdates];
}
[self performSelector:@selector(setBarButtonItems) withObject:nil afterDelay:0.1f];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self resignFirstResponder];
}
1、添加撤销功能
Cocoa Touch所提供的NSUndoManager类可用来反转用户的操作。在默认的情况下,每个应用程序的视图都会提供一份共享的撤销管理器。开发者可以使用这份共享的管理器,也可以自己创建新的。
UIResponder类的所有子类都能找到响应链中最近的那个撤销管理器。这就是说,如果在视图控制器中使用视图的NSUndoManager,那么只需通过控制器的undoManager属性,就能找到那个NSUndoManager了。这是个很方便的特性,因为开发者只需要给主视图控制器添加撤销功能,就能领其中的说有子视图西东具备该功能。
撤销管理器里面可以保存任意数量的撤销动作。开发者可以指定栈的深度。栈越深,所用的内存就越多。许多程序在内存比较紧张的时候都会只支持3、5或者10级撤销。栈中的每个动作既可以是由许多撤销操作所组成的复杂动作,又可以是例子中延时的那种简单的动作。
这个例子用撤销管理器来实现与“添加单元格”、“删除单元格”以及“移动单元格”这三种操作有关的撤销及重做功能。用户可以通过Undo和Redo按钮在自己的编辑历程之中游走。
2、实现撤销功能
例子采用同一个方法来添加及移除条目,这个方法就是updateItemAtIndexPath:withObject:。这个方法的运作方式为:如果传入的对象不是nil,那么就把它插入到索引路径中;如果是nil,则把位于该索引路径处的条目删除。
这样处理用户的请求似乎有点奇怪,因为我们需要编写一个方法,而且在方法里还要做一次判断,不过这样做其背后是有原因的。这种做法能够为撤销功能提供统一的基础代码,使得开发者可以更方便的将其同撤销管理器相集成。
因此,该方法有两件事要做。首先,它会准备好与撤销操作有关的行为。也就是说,它会告诉撤销管理器目前所运用的这项编辑操作将来应该如何还原。其次,他要执行实际的编辑操作,也就是修改items数组、更新表格,并更新导航栏中的按钮。
setBarButtonItems方法要控制Undo与Redo按钮的状态。该方法会检查当前正在活动的撤销管理器,看看撤销栈中是否提供了能够撤销及能够重做的动作。如果有,就启用相应的按钮。
这里提供了晃动撤销的功能。它在viewDidLoad方法里面把应用程序委托的appLicationSupportsShakeToEdit属性设为YES。另外也请大家注意,为了实现撤销的功能,我们调用了与第一响应者有关的两个方法,当表格视图即将出现在屏幕上面时,令其变成第一响应者,而当它要从屏幕中消失时,则令其放弃第一响应者的身份。
3、显示移除单元格所用的控件
调用[self.tableView setEditing:YES animated:YES] 方法,即可令表格把移除单元格所用的控件显示出来。这会更新表格的editing属性,并且会在每个单元格里显示出红色的移除控件。动画效果是可选的,有一条经验:如果要把程序从一个状态切换到另一个状态,那么应该在iOS界面中展示动画效果,使得用户意识到屏幕上的状态正在变化。
4、处理删除请求
删除表中的某一行时,表格会执行tableView:commitEditingStyle:forRowAtIndexPath:回调,以便将这一操作告知应用程序。从表格上移除某个条目只是令其不显示出来,这并不会修改底层的数据。除非开发者把这个条目也从数据源里移除,否则下次刷新表格的时候,这个已删除的条目还是会显示出来。我们可以在该方法里协调表格与数据源之间的关系,并对“用户想要删除这一行”的请求作出相应。
在本例中,我们可以从给数据源方法提供内容的数据结构里删除一项条目,而在真实的应用程序中,则可以执行诸如删除文件或移除联系人等操作,以响应用户的编辑请求。
我们可以把添加某行及删除某恒等操作放在beginUpdates方法之后,并放在配套的endUpdates方法之前,从而令系统能够以动画效果 来同时显示这些操作。
5、通过滑动手势删除单元格
要想从UITableView事=实例中移除条目,Swipe手势是个很简洁的方法。只需要实现tableView:commitEditingStyle:forRowAtIndexPath:方法,即可启用Swipe功能。表格会处理好剩下的事情。
用户将某个单元格从右迅速向左方拖拽,这就是针对单元格的Swipe手势,单元格右侧会显示出长方形的Delete按钮,用以确认此操作,不过左侧并不会出现表示移除单元格功能的那个红色圆形控件。
当用户执行Swipe操作并确认之后,我们就可以在tableView:commitEditingStyle:forRowAtIndexPath:方法里更新数据了,这与编辑模式下删除单元格是一样的。
6、调整单元格的顺序
如果能直接调整表格中各单元格的顺序,用户就可以获得更大的自由度了。用户可以通过重排单元格顺序来调整各项待办事物的优先级,或是在播放清单中选择首先要听的歌曲等。iOS的表格内置了重新排列顺序的功能,开发者很容易就能将其集成到应用程序中。
与通过滑动手势删除单元格一样,单元格重排功能是否启用也取决于开发者是否实现了某个方法。这个方法就是tableView:moveRowAtIndexPath:toIndexPath:,它可以将表格的数据源与屏幕上所发生的变化相同步,这与删除单元格时回调tableView:commitEditingStyle:forRowAtIndexPath:方法类似。添加这一方法即可调整单元格顺序。