我們都知道tableView有個(gè)編輯模式, 該模式下, tableView中的每一行會顯示編輯控件和排序控件. 通過這些控件你可以對cell刪除和排序, 或者增加一個(gè)新的cell. 這些control的外觀是獨(dú)特的, 如下:
當(dāng)TableView進(jìn)入編輯模式后, 用戶點(diǎn)擊了編輯控件, TableView會發(fā)送一系列消息給到DataSource和delegate
, 但是只有當(dāng)你實(shí)現(xiàn)了這些方法才會. 這些方法可以讓DataSource和delegate可以改進(jìn)row的外觀和行為; 另外通過這些方法可以讓他們執(zhí)行刪除和插入操作.
即使tableView處于正常狀態(tài), 你也可以對cell的刪除和插入, 也可刪除section, 這些操作都可以動畫展示.
下面內(nèi)容的第一部分告訴你如何對編輯模式下的TableView中的cell進(jìn)行編輯, 第二部分告訴你如何同時(shí)刪除多個(gè)cell或section.
在編輯模式下對行的刪除和插入
TableView的編輯模式
當(dāng)TableView進(jìn)入編輯狀態(tài)時(shí), tableView會受到setEditing:animated:
消息. 通常(但不一定), 該消息源于用戶點(diǎn)擊導(dǎo)航欄編輯按鈕時(shí)發(fā)送的動作消息. 可以通過delegate的tableView:editingStyleForRowAtIndexPath:
方法來控制編輯模式下, row中顯示編輯控件的樣式.
注意:如果view controller中實(shí)現(xiàn)了
setEditing:animated:animated:
方法, 那么你可以在該方法中做一些你需要的事情, 比如更新某些button的狀態(tài), controller中的這個(gè)方法會在TableView中的setEditing:animated:animated:
方法之前的被調(diào)用.
當(dāng)tableView收到setEditing:animated:
消息后, 它會給tableView中所有可見的cell發(fā)送相同的消息. 然后發(fā)送一系列消息給到DataSource和delegate(前提是實(shí)現(xiàn)了相應(yīng)的方法), 這一過程如下圖6-1所示.
當(dāng)cell收到setEditing:animated:
消息后, 接下來的方法調(diào)用序列如下:
- tableView調(diào)用DataSource中的
tableView:canEditRowAtIndexPath:
方法(如果已經(jīng)實(shí)現(xiàn)). 在這個(gè)方法中可以將一些cell排除在編輯狀態(tài)之外, 即使這些cell的editingStyle
中的值另有指示. 大多數(shù)情況下和APP不需要實(shí)現(xiàn)該方法. - tableView調(diào)用delegate中的
tableView:editingStyleForRowAtIndexPath:
方法(如果已經(jīng)實(shí)現(xiàn)). 通過該方法可以控制cell的顯示的編輯風(fēng)格和編輯控件, 到了此時(shí), tableView已經(jīng)完全進(jìn)入了編輯模式. - 然后用戶點(diǎn)擊相應(yīng)的編輯控件, 如果點(diǎn)擊刪除控件, 那么改行會展示一個(gè)刪除按鈕, 可以讓用戶確認(rèn)是否真的刪除該行.
- tableView發(fā)送
tableView:commitEditingStyle:forRowAtIndexPath:
給到DataSource對象. 盡管該協(xié)議方法時(shí)可選的, 但是要是實(shí)現(xiàn)cell的刪除和插入, 你就必須實(shí)現(xiàn)這個(gè)方法, 另外:- 實(shí)現(xiàn)
deleteRowsAtIndexPaths:withRowAnimation:
或者insertRowsAtIndexPaths:withRowAnimation:
來調(diào)整TableView的顯示 - 更新相應(yīng)的模型數(shù)據(jù)數(shù)組, 往數(shù)組刪除相應(yīng)的數(shù)據(jù)或者添加相應(yīng)的數(shù)據(jù).
- 實(shí)現(xiàn)
當(dāng)用輕掃cell來展示刪除按鈕時(shí), 在此過程中方法調(diào)用和上圖6-1所示有點(diǎn)不同. 當(dāng)用輕掃cell來刪除時(shí), TableView會先檢查DataSource是否實(shí)現(xiàn)了tableView:commitEditingStyle:forRowAtIndexPath:
方法; 如果實(shí)現(xiàn)了該方法, TableView會發(fā)送setEditing:animated:
消息給自己后進(jìn)入編輯模式. 在"swipe to delete"這種模式下, TableView不會展示添加/移動等控件. 因?yàn)樵撌录怯捎脩趄?qū)動的, TableView會調(diào)用delegate的兩個(gè)方法tableView:willBeginEditingRowAtIndexPath:
和tableView:didEndEditingRowAtIndexPath:
. 通過實(shí)現(xiàn)這兩個(gè)方法, 可以適當(dāng)?shù)馗耇ableView的外觀.
注意:在DataSource的
tableView:commitEditingStyle:forRowAtIndexPath:
方法中, 記得不要調(diào)用setEditing:animated:
方法, 如果因?yàn)槟承┰蚍堑谜{(diào)用, 那么可以使用performSelector:withObject:afterDelay:
來延遲調(diào)用該方法.
雖然你可以通過cell中的插入控件來插入新的cell, 當(dāng)這里有種替代方法, 就是在導(dǎo)航欄中添加一個(gè)"Add"按鈕, 點(diǎn)擊該按鈕發(fā)送一個(gè)action給到viewController, 然后modal出一個(gè)新view, 用來給用戶輸入新的item, 該view會遮住TableView. 當(dāng)新的item輸入完成后, viewController會更新data, 然后reload TableView. 下面的內(nèi)容會提到該方法.
刪除cell的一個(gè)例子
該部分內(nèi)容通過一個(gè)例子向你展示TableView的搭建和處理TableView編輯相關(guān)的實(shí)現(xiàn). 該demo使用導(dǎo)航欄的控制器結(jié)構(gòu)來管理TableView. 在loadView
方法中, 創(chuàng)建TableView和設(shè)置DataSource和delegate, 然后將導(dǎo)航欄的右邊的barButton設(shè)置為edit button.
self.navigationItem.rightBarButtonItem = self.editButtonItem;
該按鈕已經(jīng)設(shè)置好了點(diǎn)擊的action為發(fā)送setEditing:animated:
消息給到view controller; button的title會在"Edit"和"Done"來回切換. 代碼6-1展示了該方法的實(shí)現(xiàn).
代碼清單6-1 view controller對setEditing:animated:
方法的反應(yīng)
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[tableView setEditing:editing animated:YES];
if (editing) {
addButton.enabled = NO;
} else {
addButton.enabled = YES;
}
}
當(dāng)TableView進(jìn)入編輯模式下, viewController會讓所有的cell顯示一個(gè)刪除控件, 除了最后一個(gè)cell外. 最后一個(gè)cell會顯示一個(gè)插入控件. 這些是通過tableView:editingStyleForRowAtIndexPath:
實(shí)現(xiàn)的, 如代碼6-2.
代碼清單6-2 設(shè)置行的編輯風(fēng)格
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
SimpleEditableListAppDelegate *controller = (SimpleEditableListAppDelegate *)[[UIApplication sharedApplication] delegate];
if (indexPath.row == [controller countOfList]-1) {
return UITableViewCellEditingStyleInsert;
} else {
return UITableViewCellEditingStyleDelete;
}
}
當(dāng)用戶點(diǎn)擊行中的delete控件時(shí), TableView會調(diào)用viewController中的tableView:commitEditingStyle:forRowAtIndexPath:
方法, 如代碼6-3所示, 在該方法的實(shí)現(xiàn)是刪除相應(yīng)的數(shù)據(jù)項(xiàng), 然后給tableView發(fā)送一個(gè)deleteRowsAtIndexPaths:withRowAnimation:
消息.
代碼清單6-3 更新數(shù)據(jù)源和刪除相應(yīng)的行
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// 如果此行被刪除, 那么將其從列表中移除.
if (editingStyle == UITableViewCellEditingStyleDelete) {
SimpleEditableListAppDelegate *controller = (SimpleEditableListAppDelegate *)[[UIApplication sharedApplication] delegate];
[controller removeObjectFromListAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
添加一個(gè)cell的例子
這部分內(nèi)容告訴你如何往tableView中添加cell. 在本例中, 不是通過cell上的插入控件來引發(fā)添加操作, 而是通過在導(dǎo)航欄上添加一個(gè)"Add"button來開啟添加操作. 下面代碼6-4, 展示了viewController的loadView
實(shí)現(xiàn), 將addButton設(shè)置為導(dǎo)航欄的右側(cè)barButton.
代碼清單6-4 往導(dǎo)航欄中添加一個(gè)Add button
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addItem:)];
self.navigationItem.rightBarButtonItem = addButton;
注意控制器還需要設(shè)置控件的標(biāo)題和控件action的selector. 當(dāng)用戶點(diǎn)擊Add按鈕時(shí)會調(diào)用addItem:
方法. 該action方法的實(shí)現(xiàn)如6-5所示. 在該方法中, 會創(chuàng)建一個(gè)包含一個(gè)viewController的navigationController, 然后將其modal出來.
代碼清單6-5 Add按鈕的action方法
- (void)addItem:sender {
if (itemInputController == nil) {
itemInputController = [[ItemInputController alloc] init];
}
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:itemInputController];
[[self navigationController] presentModalViewController:navigationController animated:YES];
}
在新modal出的controller中, 有個(gè)textfield和一個(gè)save按鈕, 用戶輸入新的內(nèi)容后, 點(diǎn)擊save按鈕會調(diào)動controller中的save:
方法, 如下代碼6-6所示, viewController會從textfield提取用戶輸入的信息, 然后使用該信息去更新tableView的data數(shù)組.
代碼清單6-6 往數(shù)據(jù)模型數(shù)組中添加一個(gè)新item
- (void)save:sender {
UITextField *textField = [(EditableTableViewTextField *)[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] textField];
SimpleEditableListAppDelegate *controller = (SimpleEditableListAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *newItem = textField.text;
if (newItem != nil) {
[controller insertObject:newItem inListAtIndex:[controller countOfList]];
}
[self dismissModalViewControllerAnimated:YES];
}
當(dāng)modal出的view被dismiss后, TableView會被reloaded, 之后新添加的item會被顯示出來.
批量插入/刪除/排序tableView中的行和組
UITableView
類允許你在同一時(shí)間將一組行或section插入/刪除, 并且以動畫展示. 在代碼6-7中, 有8個(gè)方法用來批量刪除和插入. 注意, 你可以在animation-block的外面調(diào)用這些刪除和插入方法(就如前面內(nèi)容中所述的DataSource tableView:commitEditingStyle:forRowAtIndexPath:
方法實(shí)現(xiàn)一樣).
代碼清單6-7 刪除/插入的批處理方法
- (void)beginUpdates;
- (void)endUpdates;
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation: (UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation: (UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
注意:
reloadSections:withRowAnimation:
和reloadRowsAtIndexPaths:withRowAnimation:
這兩個(gè)方法是在iOS3.0引入的, 這些方法可以讓tableView重載具體的sections和rows, 而不是調(diào)用reloadData
來更新整體可見的tableView.
為了動畫展示批量插入/刪除/重排序, 你需要在beginUpdates
和endUpdates
兩個(gè)方法調(diào)用之間調(diào)用前面的批處理方法. 如果你不這樣做, 調(diào)用批處理方法后, row和section的index可能會變的無效了. 另外beginUpdates
和endUpdates
這兩個(gè)方法可以嵌套使用.
總而言之, 在調(diào)用endUpdates
方法返回后, tableView回去訪問DataSource和delegate對象, 請求行和數(shù)據(jù). 所以在刪除和插入時(shí)應(yīng)該更新tableView背景的數(shù)據(jù)模型數(shù)組.
批量刪除和插入的例子
為了批量插入或者刪除一組行/組, 第一是準(zhǔn)備好數(shù)組用來存放rows和sections背后的數(shù)據(jù). 當(dāng)行和組被插入和刪除后, 從該數(shù)組中獲取數(shù)據(jù)來填充行和組.
接下來是, 調(diào)用beginUpdates
方法, 然后是調(diào)用insertRowsAtIndexPaths:withRowAnimation:
, deleteRowsAtIndexPaths:withRowAnimation:
, insertSections:withRowAnimation:
, 或者insertSections:withRowAnimation:
, 然后使用endUpdates
方法來提交上述操作. 代碼6-8展示了這一操作.
代碼清單6-8 批量刪除/插入操作
- (IBAction)insertAndDeleteRows:(id)sender {
// original rows: Arizona, California, Delaware, New Jersey, Washington
[states removeObjectAtIndex:4]; // Washington
[states removeObjectAtIndex:2]; // Delaware
[states insertObject:@"Alaska" atIndex:0];
[states insertObject:@"Georgia" atIndex:3];
[states insertObject:@"Virginia" atIndex:5];
NSArray *deleteIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:4 inSection:0],
nil];
NSArray *insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0],
[NSIndexPath indexPathForRow:5 inSection:0],
nil];
UITableView *tv = (UITableView *)self.view;
[tv beginUpdates];
[tv insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
[tv deleteRowsAtIndexPaths:deleteIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tv endUpdates];
// ending rows: Alaska, Arizona, California, Georgia, New Jersey, Virginia
}
排序操作和Index Paths
你可能注意到了代碼6-8中deleteRowsAtIndexPaths:withRowAnimation:
方法要在insertRowsAtIndexPaths:withRowAnimation:
方法后. 但是tableView執(zhí)行的操作不一定是按照block中的方法調(diào)用順序. 事實(shí)上, TableView會將所有的插入操作延后, 直到處理完刪除操作. TableView的行為與更新塊內(nèi)調(diào)用的重新加載方法相同——在執(zhí)行動畫塊之前耻台,對行和節(jié)的索引進(jìn)行重新加載。這種行為和插入/刪除/排序的順序無關(guān).
刪除和熱loading操作會決定原TableView中的那些行/組需要?jiǎng)h除或者更新位置; 插入操作會指定向TableView中加入那些行/組. IndexPath用來標(biāo)記row和section的位置, 另一方面俺孙,插入或移除可變數(shù)組中的項(xiàng)可能會影響用于連續(xù)插入或移除操作的數(shù)組索引搬俊;例如,如果在某個(gè)索引處插入項(xiàng)隙姿,則數(shù)組中所有后續(xù)項(xiàng)的索引都會遞增梅垄。
這里有個(gè)例子, 假設(shè)你有個(gè)TableView, 其中有三個(gè)section, 每個(gè)section中有三個(gè)row, 然后實(shí)現(xiàn)下面的動畫塊:
- Begin updates
- 刪除索引為(row:1, section:0)處的行
- 刪除索引為(section:1)處的組
- 往索引(row:1, section:1)處插入一行
- End updates
圖6-2, 展示了該動畫塊執(zhí)行的過程