UITableView折叠分区时粘性头部消失问题的修复咨询
UITableView折叠分区时Header消失的优化方案
问题描述
实现了带自定义Section Header的UITableView,支持点击Header展开/折叠对应分区,滚动时Section Header会固定在屏幕顶部。但存在体验问题:当滚动某分区至一半位置时点击其Header折叠,当前Section Header会消失并被下一个分区的Header替代,用户需上滚才能查看原分区。尝试过tableView.scrollTo方法但效果不佳,希望找到能让折叠时当前Section Header保持可视的方案。
现有代码实现
var hiddenSections = Set<Int>() let tableViewData = [ ["1","2","3","4","5", "6", "7", "8", "9", "10"], ["1","2","3","4","5", "6", "7", "8", "9", "10"], ["1","2","3","4","5", "6", "7", "8", "9", "10"], ["1","2","3","4","5", "6", "7", "8", "9", "10"], ["1","2","3","4","5", "6", "7", "8", "9", "10"], ] func numberOfSections(in tableView: UITableView) -> Int { return self.tableViewData.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 50 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if self.hiddenSections.contains(section) { return 0 } return self.tableViewData[section].count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell() cell.textLabel?.text = self.tableViewData[indexPath.section][indexPath.row] return cell } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let sectionButton = UIButton() sectionButton.setTitle(String(section), for: .normal) sectionButton.backgroundColor = .systemBlue sectionButton.tag = section sectionButton.addTarget(self, action: #selector(self.hideSection(sender:)), for: .touchUpInside) return sectionButton } @objc private func hideSection(sender: UIButton) { let section = sender.tag func indexPathsForSection() -> [IndexPath] { var indexPaths = [IndexPath]() for row in 0..<self.tableViewData[section].count { indexPaths.append(IndexPath(row: row, section: section)) } return indexPaths } self.tableView.beginUpdates() if self.hiddenSections.contains(section) { self.hiddenSections.remove(section) self.tableView.insertRows(at: indexPathsForSection(), with: .fade) } else { self.hiddenSections.insert(section) self.tableView.deleteRows(at: indexPathsForSection(), with: .fade) } self.tableView.endUpdates() }
解决方案
问题核心是折叠分区后,TableView的contentOffset会因内容高度变化自动调整,导致当前Header被顶出可视区域。可以通过在折叠后手动修正contentOffset,让目标Section Header保持在屏幕顶部。
修改hideSection方法如下:
@objc private func hideSection(sender: UIButton) { let section = sender.tag func indexPathsForSection() -> [IndexPath] { var indexPaths = [IndexPath]() for row in 0..<self.tableViewData[section].count { indexPaths.append(IndexPath(row: row, section: section)) } return indexPaths } // 记录当前Header的位置及可视状态 let headerRect = tableView.rectForHeader(inSection: section) let isHeaderVisible = tableView.bounds.contains(headerRect) self.tableView.beginUpdates() let wasHidden = self.hiddenSections.contains(section) if wasHidden { self.hiddenSections.remove(section) self.tableView.insertRows(at: indexPathsForSection(), with: .fade) } else { self.hiddenSections.insert(section) self.tableView.deleteRows(at: indexPathsForSection(), with: .fade) } self.tableView.endUpdates() // 折叠后若Header原本可见,滚动至Header顶部 if !wasHidden && isHeaderVisible { let targetOffset = CGPoint(x: 0, y: headerRect.origin.y - tableView.contentInset.top) tableView.setContentOffset(targetOffset, animated: true) } }
逻辑说明
- 折叠前先获取当前Section Header的位置,判断其是否处于屏幕可视区域内
- 执行完折叠的更新操作后,若Header原本可见,计算出使Header刚好位于屏幕顶部的contentOffset
- 通过
setContentOffset滚动到目标位置,确保用户点击的Header折叠后仍停留在可视区域顶部
内容的提问来源于stack exchange,提问作者Hien Ho
相关产品推荐
相关产品推荐

