如何修复UITableView插入/删除行时Fade动画的异常重叠问题
问题原因及解决方案
问题原因
- 协议方法归属错误:
numberOfSections(in:)和tableView(_:numberOfRowsInSection:)属于UITableViewDataSource协议,但你将其放在了UITableViewDelegate扩展中。这会导致UITableView无法正确获取各section的行数信息,批量更新行时位置计算偏差,最终引发单元格重叠。 - Header视图重复添加子控件:每次复用Header都会调用
setLayout(),重复执行addSubview(expandButton),造成视图层级冗余,间接干扰布局计算。 - 行高设置冗余:当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
相关产品推荐
相关产品推荐

