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

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)
    }
}

逻辑说明

  1. 折叠前先获取当前Section Header的位置,判断其是否处于屏幕可视区域内
  2. 执行完折叠的更新操作后,若Header原本可见,计算出使Header刚好位于屏幕顶部的contentOffset
  3. 通过setContentOffset滚动到目标位置,确保用户点击的Header折叠后仍停留在可视区域顶部

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 15:40:48