如何删除动态调整高度的UITableView行并消除空白行?
解决UITableView删除行后出现空白行的问题
看起来你的问题出在手动调整tableView frame的时机和方式不对,加上在viewDidLayoutSubviews里调用reloadData()导致布局状态混乱。我来帮你梳理下问题根源和解决办法:
问题分析
- 你直接手动修改
tableView.frame来调整高度,但删除行后,tableView的contentSize不会立刻更新(因为删除行的动画还在进行),这时候获取的contentSize.height还是旧值,导致表格高度没有正确缩小。 - 手动调用
viewDidLayoutSubviews()是错误的——这个方法是系统用来处理布局的回调,手动调用会打乱系统的布局流程,而且你在里面还加了reloadData(),会触发重复布局,进一步加剧问题。
推荐解决方案:改用Auto Layout约束(更稳定)
手动调整frame是比较老旧的做法,Auto Layout能更可靠地处理动态布局。按以下步骤修改:
1. 在Storyboard中设置约束
- 给tableView添加顶部、左侧、右侧的约束,固定它的位置和宽度。
- 给tableView添加一个高度约束,并把这个约束关联成IBOutlet(比如命名为
tableViewHeightConstraint)。 - 给newButton设置水平居中约束,再设置一个距离tableView底部80pt的约束——这样按钮位置会自动跟着tableView高度变化,不用手动修改frame!
2. 修正代码
首先添加高度约束的IBOutlet:
@IBOutlet weak var tableView: UITableView! @IBOutlet weak var newButton: UIButton! @IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint! // 新增的高度约束 var items: [String] = ["Swift", "Is", "So", "Amazing"]
然后移除所有手动修改frame的代码(viewWillAppear和viewDidLayoutSubviews里的frame设置都删掉),修改删除行的逻辑:
// 允许单元格删除 func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in // 删除数据 self.items.remove(at: indexPath.row) // 执行删除动画 tableView.deleteRows(at: [indexPath], with: .fade) // 等待动画完成后更新表格高度约束 DispatchQueue.main.async { self.tableViewHeightConstraint.constant = self.tableView.contentSize.height self.view.layoutIfNeeded() } print(self.items) print("Number of rows: \(tableView.numberOfRows(inSection: 0))") } delete.backgroundColor = UIColor.blue return [delete] }
在viewWillAppear里初始化表格高度:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 初始化表格高度 tableViewHeightConstraint.constant = tableView.contentSize.height view.layoutIfNeeded() // 设置表格边框 tableView.layer.borderWidth = 1 tableView.layer.borderColor = UIColor.black.cgColor }
最后,修正viewDidLayoutSubviews,只保留必要的逻辑(不要加reloadData!):
override func viewDidLayoutSubviews(){ super.viewDidLayoutSubviews() // 必须调用super // 如果需要确保高度始终正确,可以在这里再更新一次 if tableViewHeightConstraint.constant != tableView.contentSize.height { tableViewHeightConstraint.constant = tableView.contentSize.height } }
如果你坚持用手动调整frame
如果不想改Auto Layout,那要确保在删除动画完成后再更新高度:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in // 用CATransaction监听动画完成 CATransaction.begin() CATransaction.setCompletionBlock { // 动画结束后更新表格高度和按钮位置 self.updateTableAndButtonLayout() } self.items.remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .fade) CATransaction.commit() print(self.items) print("Number of rows: \(tableView.numberOfRows(inSection: 0))") } delete.backgroundColor = UIColor.blue return [delete] } // 单独的布局更新方法 private func updateTableAndButtonLayout() { // 更新表格高度 tableView.frame.size.height = tableView.contentSize.height // 更新按钮位置 let tableViewBottom = tableView.frame.origin.y + tableView.frame.size.height newButton.frame.origin.y = tableViewBottom + 80 }
同时,要把viewDidLayoutSubviews里的reloadData()删掉,避免循环布局。
关键提醒
- 永远不要在
viewDidLayoutSubviews里调用reloadData(),这会触发无限循环的布局刷新。 - 确保tableView的
rowHeight设置正确(比如固定高度或者UITableView.automaticDimension),这样contentSize的计算才会准确。
内容的提问来源于stack exchange,提问作者Everett
相关产品推荐
相关产品推荐

