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

如何修复UITableView插入/删除行时Fade动画的异常重叠问题

问题原因及解决方案

问题原因

  1. 协议方法归属错误:numberOfSections(in:) 和 tableView(_:numberOfRowsInSection:) 属于UITableViewDataSource协议,但你将其放在了UITableViewDelegate扩展中。这会导致UITableView无法正确获取各section的行数信息,批量更新行时位置计算偏差,最终引发单元格重叠。
  2. Header视图重复添加子控件:每次复用Header都会调用setLayout(),重复执行addSubview(expandButton),造成视图层级冗余,间接干扰布局计算。
  3. 行高设置冗余:当section收起时,numberOfRowsInSection已返回0,heightForRowAt不会被调用,此时返回0的逻辑完全多余,反而容易造成逻辑混淆。

解决方案

1. 修正协议方法归属

将numberOfSections(in:)和tableView(_:numberOfRowsInSection:)移至UITableViewDataSource扩展,并删除UITableViewDelegate中的对应方法:

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return datas.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datas[section].isExpanded ? datas[section].items.count : 0
    }
    
    // 原有的cellForRowAt、heightForHeaderInSection等方法...
}

2. 避免Header重复添加子控件

将Header的布局代码移至初始化方法,确保只执行一次:

class CopplasibleTableViewHeader: UITableViewHeaderFooterView {
    weak var collapsibleTableViewHeaderDelegate: CollapsibleTableViewHeaderDelegate?
    
    lazy var expandButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
        button.addTarget(self, action: #selector(handleExpandButton), for: .touchUpInside)
        return button
    }()
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        setupLayout()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupLayout()
    }
    
    private func setupLayout() {
        addSubview(expandButton)
        expandButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            expandButton.centerXAnchor.constraint(equalTo: centerXAnchor),
            expandButton.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }
    
    @objc private func handleExpandButton(button: UIButton) {
        collapsibleTableViewHeaderDelegate?.didTapExpandButton(tag: button.tag)
    }
}

同时在ViewController的viewForHeaderInSection中删除header?.setLayout()调用。

3. 简化行高逻辑

删除heightForRowAt方法,直接在viewDidLoad中设置固定行高:

override func viewDidLoad() {
    super.viewDidLoad()
    // 其他初始化代码...
    tableView.rowHeight = 50
}

当section收起时行数为0,自然不会显示行,无需额外设置行高为0。

4. 优化批量更新逻辑(可选)

简化didTapExpandButton中的indexPaths生成代码:

extension ViewController: CollapsibleTableViewHeaderDelegate {
    func didTapExpandButton(tag: Int) {
        datas[tag].isExpanded.toggle()
        let indexPaths = datas[tag].items.indices.map { IndexPath(row: $0, section: tag) }
        
        tableView.performBatchUpdates {
            if datas[tag].isExpanded {
                tableView.insertRows(at: indexPaths, with: .fade)
            } else {
                tableView.deleteRows(at: indexPaths, with: .fade)
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者akaakoz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:46:00