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

Expandable Collapse TableView固定行至顶部异常及NSRangeException报错求助

TableView折叠展开问题与NSRangeException异常修复

问题描述

我做了一个可展开折叠的TableView,展开时能把行固定在顶部,但关闭顶部已展开的行后,该行会回到原位置,我希望关闭后它仍保持在顶部。现在我的hideSection函数触发了NSRangeException异常,报错信息如下:

Terminating app due to uncaught exception 'NSRangeException', reason: 'Attempted to scroll the table view to an out-of-bounds row'

相关代码:

@objc private func hideSection(sender: UIButton) {
        let section = sender.tag
        let index = IndexPath(row: 0, section: section)
        
        func indexPathsForSection() -> [IndexPath] {
            var indexPaths = [IndexPath]()
            for row in 0..<self.tableViewData[section].count {
                indexPaths.append(IndexPath(row: row, section: section))
            }
            return indexPaths
        }
        
        tableViewContentOffset = tableView.contentOffset
        
        if self.hiddenSections.contains(section) {
            self.hiddenSections.remove(section)
            self.tableView.insertRows(at: indexPathsForSection(), with: .fade)
            
            if tableView.numberOfRows(inSection: section) > 0 {
                tableView.scrollToRow(at: index, at: .top, animated: true)
            }
            
        } else {
            self.hiddenSections.insert(section)
            self.tableView.deleteRows(at: indexPathsForSection(), with: .fade)
            
            tableView.scrollToRow(at: index, at: .top, animated: true)
            
        }
    }

问题分析与修复

1. 异常根源

在折叠分支(hiddenSections.insert(section))中,你先删除了该section的所有行,紧接着调用scrollToRow(at: index, at: .top, animated: true)——此时该section已经没有任何行,IndexPath(row:0, section:section)属于越界索引,直接触发NSRangeException。

2. 实现“关闭后保持顶部”的正确逻辑

要让关闭后的section停在顶部,不能滚动到已删除的行,而是要直接定位到section的头部位置。修复后的代码如下:

@objc private func hideSection(sender: UIButton) {
    let section = sender.tag
    let index = IndexPath(row: 0, section: section)
    
    func indexPathsForSection() -> [IndexPath] {
        var indexPaths = [IndexPath]()
        for row in 0..<self.tableViewData[section].count {
            indexPaths.append(IndexPath(row: row, section: section))
        }
        return indexPaths
    }
    
    // 提前获取section头部的位置,用于折叠后定位
    let sectionHeaderRect = tableView.rectForHeader(inSection: section)
    let targetOffset = CGPoint(x: 0, y: sectionHeaderRect.origin.y - tableView.contentInset.top)
    
    if self.hiddenSections.contains(section) {
        self.hiddenSections.remove(section)
        self.tableView.insertRows(at: indexPathsForSection(), with: .fade)
        
        if tableView.numberOfRows(inSection: section) > 0 {
            tableView.scrollToRow(at: index, at: .top, animated: true)
        }
    } else {
        self.hiddenSections.insert(section)
        self.tableView.deleteRows(at: indexPathsForSection(), with: .fade)
        
        // 滚动到section头部,替代无效的scrollToRow调用
        tableView.setContentOffset(targetOffset, animated: true)
    }
}

关键修改点

  • 折叠操作前,先计算section头部的准确偏移量,包含导航栏等内边距的修正
  • 折叠完成后,用setContentOffset直接定位到section头部,既避免了越界异常,又实现了“保持顶部”的需求

内容的提问来源于stack exchange,提问作者Burak Köse

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 04:05:16