You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何删除动态调整高度的UITableView行并消除空白行?

解决UITableView删除行后出现空白行的问题

看起来你的问题出在手动调整tableView frame的时机和方式不对,加上在viewDidLayoutSubviews里调用reloadData()导致布局状态混乱。我来帮你梳理下问题根源和解决办法:

问题分析

  1. 你直接手动修改tableView.frame来调整高度,但删除行后,tableView的contentSize不会立刻更新(因为删除行的动画还在进行),这时候获取的contentSize.height还是旧值,导致表格高度没有正确缩小。
  2. 手动调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:23:55