UITableView移动删除行异常:willDisplay/didEndDisplaying调用问题排查
我之前帮好几个开发者排查过类似的问题——当你在beginUpdates()和endUpdates()(或performBatchUpdates)里同时执行将cell移动到indexPath(0,0) + 从数据源数组移除这两个操作时,UITableView的复用回调(willDisplayCell、didEndDisplayingCell)会出现异常:要么didEndDisplayingCell不触发,要么willDisplayCell被多次调用,最终导致cell重复绑定逻辑(比如你例子里的label重复加"1")。
问题根源
这其实是UITableView批量更新的内部逻辑bug:当移动和移除操作在同一批更新里执行时,tableView无法正确处理这两个操作的依赖关系,导致cell的显示状态判断混乱,进而触发错误的回调。
具体解决方案
1. 拆分移动和移除操作,分批次执行
不要把移动和移除放在同一个begin/endUpdates块里,而是先完成移动操作,再在更新完成的回调里执行移除。这样tableView能分别处理每一步的状态变更,避免冲突。
示例代码:
// 先执行移动操作 tableView.performBatchUpdates({ let sourceIndex = IndexPath(row: yourTargetRow, section: 0) // 你要移动的cell原位置 let targetIndex = IndexPath(row: 0, section: 0) // 同步更新数据源 yourDataSourceArray.move(fromOffsets: IndexSet(integer: sourceIndex.row), toOffset: targetIndex.row) // 通知tableView移动cell tableView.moveRow(at: sourceIndex, to: targetIndex) }, completion: { [weak self] isFinished in guard isFinished, let self = self else { return } // 移动完成后执行移除操作 let removeIndex = IndexPath(row: 0, section: 0) // 先更新数据源 self.yourDataSourceArray.remove(at: removeIndex.row) // 再通知tableView删除cell self.tableView.performBatchUpdates({ self.tableView.deleteRows(at: [removeIndex], with: .automatic) }, completion: nil) })
2. 手动管理cell状态,不依赖回调的可靠性
因为回调可能异常,所以要通过prepareForReuse()重置cell状态,同时在cellForRowAt里设置初始状态,确保cell每次复用都是干净的。
示例代码:
// 在自定义Cell类里重写prepareForReuse override func prepareForReuse() { super.prepareForReuse() // 重置label内容,移除之前添加的"1" if let currentText = self.label.text, currentText.hasSuffix("1") { self.label.text = String(currentText.dropLast()) } } // 在tableView的cellForRowAt里设置初始状态 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as! YourCustomCell // 直接设置cell的初始文本,不要依赖willDisplay let data = yourDataSourceArray[indexPath.row] cell.label.text = data return cell }
3. 确保数据源操作与tableView操作完全同步
无论哪种更新操作,先修改数据源,再通知tableView更新是必须遵守的规则。如果数据源和tableView的状态不一致,会进一步加剧回调异常的问题。
总结
拆分操作能解决tableView内部状态混乱的问题,而手动管理cell状态则能在回调异常时提供兜底保障,两者结合就能彻底解决你遇到的cell重复绑定、回调异常问题。
内容的提问来源于stack exchange,提问作者trickster77777

