本文主要介紹UITableView中,如何在多個Section間,移動、刪除、插入Cell
UITableView為我們提供了以下幾個函數,從參數類型可以看出,可以同時插入刪除多個Cell或Section,但移動只能移動一個Cell或Section。
open func insertRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation)
open func insertSections(_ sections: IndexSet, with animation: UITableViewRowAnimation)
open func deleteRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation)
open func deleteSections(_ sections: IndexSet, with animation: UITableViewRowAnimation)
@available(iOS 5.0, *)
open func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath)
@available(iOS 5.0, *)
open func moveSection(_ section: Int, toSection newSection: Int)
效果圖如下

左滑刪除
TableView中提供了 editActionsForRowAt indexPath這個方法,可以添加左滑按鈕,但具體的刪除操作需要我們自己完成。從這個函數的返回值中可以看出,我們能夠添加多個按鈕。但關于左滑按鈕的自定義,我們可以直接控制按鈕的背景顏色和文字顏色,若想添加按鈕圖片,可以直接在cell右側添加一張圖片覆蓋左滑按鈕。
@available(iOS 8.0, *)
optional public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
代碼如下
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "刪除") { (action, actionIndexPath) in
//刪除操作
}
let otherAction = UITableViewRowAction(style: .destructive, title: "其他") { (action, actionIndexPath) in
//其他操作
}
return [deleteAction]
}
注意事項
在刪除操作前,我們需要對TableView的數據進行操作。在調用deleteRows時,會自動調用numberOfRowsInSection和numberOfSections這兩個函數。即系統(tǒng)會判斷刪除操作前,cell和section的個數是否已經修改,若未修改會直接crash并提示以下錯誤。另外,當刪除最后一個Cell時,不能調用deleteRows而應該調用deleteSections。
reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (6) must be equal to the number of rows contained in that section before the update (6), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
代碼如下
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
//創(chuàng)建刪除操作
let deleteAction = UITableViewRowAction(style: .destructive, title: "刪除") { (action, actionIndexPath) in
if indexPath.section == 0 && self.dataArray1.count != 0 { //判斷刪除的是哪個section的cell
self.dataArray1.remove(at: indexPath.row)
///注意: 修改數據后判斷當前section是否已經沒有數據,若沒有則需要刪除整個section,若刪除cell會報錯
if self.dataArray1.count == 0 {
tableView.deleteSections([0], with: .automatic)
} else {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
} else {
self.dataArray2.remove(at: indexPath.row)
if self.dataArray2.count == 0 { //同上
tableView.deleteSections([indexPath.section], with: .automatic)
} else {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
return [deleteAction]
}
Cell的移動和插入
TableView提供一下四個函數用于移動和插入Cell,這里需要注意的地方跟刪除一樣。在移動和插入之前需要先對數據進行操作,若沒有修改cell的行數和對應的section個數,就這會crash。移動Cell還有一個需要注意的地方,在移動Section中最后一個Cell或將一個Cell移動到新的Section時,不能調用moveRow函數,需要將移動操作拆分為插入和刪除操作。因為在新增第一個Cell或刪除最后一個Cell,調用moveRow函數時,cell的indexpath已經做了更改,這時系統(tǒng)認為將一個cell移動到一個不存在的位置,直接crash。插入操作和刪除操作類似,若Section在插入前并沒有Cell,需要調用insertSections來完成插入。
@available(iOS 5.0, *)
open func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath)
@available(iOS 5.0, *)
open func moveSection(_ section: Int, toSection newSection: Int)
open func insertRows(at indexPaths: [IndexPath], with animation: UITableViewRowAnimation)
open func insertSections(_ sections: IndexSet, with animation: UITableViewRowAnimation)
點擊TableView時移動Cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//點擊時,將cell移動到另一個section
if indexPath.section == 0 && dataArray1.count != 0 {
let titleString = dataArray1[indexPath.row]
dataArray1.remove(at: indexPath.row)
//注意刪除一條數據后,若該Section數據個數為0,即Cell個數為0時,需要刪除整個Section
//同時無法再使用moveRow函數,因為Section有變化,需要調用insertRows來完成移動操作
if dataArray1.count == 0 {
//刪除section
tableView.deleteSections([0], with: .top)
//更新數據
dataArray2.insert(titleString, at: 0)
//判斷是否是添加一個新的Section
if dataArray2.count == 1 { //添加一個Section
tableView.insertSections([1], with: .top)
} else {
//更新數據后,插入cell
tableView.insertRows(at: [IndexPath(item: 0, section: 0)], with: .top)
}
} else {
if dataArray2.count == 0 { //添加一個Section
//先做刪除修改
tableView.deleteRows(at: [indexPath], with: .fade)
//刪除完成后,插入數據,并觸發(fā)插入動畫
dataArray2.insert(titleString, at: 0)
tableView.insertSections([1], with: .top)
} else {
dataArray2.insert(titleString, at: 0)
tableView.moveRow(at: indexPath, to: IndexPath(item: 0, section: 1))
}
}
} else {
let titleString = dataArray2[indexPath.row]
dataArray2.remove(at: indexPath.row)
if dataArray2.count == 0 {
var deleteSection = 1 ///判斷當前是哪一個Section
if indexPath.section == 0 {
deleteSection = 0
}
tableView.deleteSections([deleteSection], with: .fade)
dataArray1.insert(titleString, at: 0)
if dataArray1.count == 1 { //添加一個Section
tableView.insertSections([0], with: .top)
} else {
tableView.insertRows(at: [IndexPath(item: 0, section: 0)], with: .top)
}
} else {
if dataArray1.count == 0 { //添加一個Section
tableView.deleteRows(at: [indexPath], with: .bottom)
dataArray1.insert(titleString, at: 0)
tableView.insertSections([0], with: .top)
} else {
dataArray1.insert(titleString, at: 0)
tableView.moveRow(at: indexPath, to: IndexPath(item: 0, section: 0))
}
}
}
}
總結
個人認為,可以將移動刪除插入這幾個動作,看成是系統(tǒng)為TableView刷新添加了一個動畫。我們在執(zhí)行這幾個動畫前,已經對數據進行了修改,然后添加動畫展示給用戶看,當然如果直接調用reloadData也可以實現(xiàn)效果,只是沒有動畫而已。這里需要注意一點,這幾個動作都只會重新調用numberOfRowsInSection和numberOfSections這兩個函數,并不會執(zhí)行cellForRowAt indexPath,因此如果在cellForRow中進行移動刪除和插入操作時,需要重新獲取indexpath