SLExpandableTableView源码解析(上):numberOfRowsInSection如何把一个分区的多行折叠成1行
2026/8/23 11:44:29 网站建设 项目流程

SLExpandableTableView源码解析(上):numberOfRowsInSection如何把一个分区的多行折叠成1行

【免费下载链接】SLExpandableTableViewSLExpandableTableView is a UITableView subclass that gives you easy access to expandable and collapsable sections by just implementing a few more delegate and dataSource protocols. (iPhone, iPad, iOS)项目地址: https://gitcode.com/gh_mirrors/sl/SLExpandableTableView

SLExpandableTableView 是一款专为 iOS 打造的开源组件,它是 UITableView 的子类,只需实现几个额外的 DataSource / Delegate 方法,就能让列表的分区(Section)获得"一键展开 / 折叠"的手风琴效果,同时支持 iPhone 与 iPad。本文聚焦源码中一个最巧妙的技巧:库如何利用numberOfRowsInSection这个回调,把分区里的多行数据"折叠"成 1 行。

🎯 一、这个组件解决了什么问题

原生 UITableView 并不会自动提供分区折叠能力——想让某个 Section 收起来,你得自己挪动数据、重算行数,代码又长又容易出动画 bug。SLExpandableTableView 的思路是:把行数的"记账权"抢过来。它成为 tableView 真正的 dataSource,对外再暴露一层自己的协议,由它决定"每个分区现在显示几行"。

效果如下图所示:黄色单元格是数据行,白色带箭头的行就是折叠/展开的"开关行":

对使用方来说,只需额外实现 3 个数据源方法(定义在 SLExpandableTableView/SLExpandableTableView.h):

  • canExpandSection:—— 这个分区是否可展开
  • needsToDownloadDataForExpandableSection:—— 展开前是否需要先拉数据
  • expandingCellForSection:—— 返回那个"开关行"单元格的样式

🔑 二、关键前提:第 0 行被"征用"了

看懂折叠原理前,先要知道库的一个约定:在可展开的分区里,row 0 永远不是你的数据行,而是那个展开/折叠开关行。你的数据从 row 1 开始排。

看示例工程 Tests/SLExpandableTableViewTests/SLExpandableTableViewController.m 中的实现就很清楚:一个分区有 4 条数据,但返回的行数是dataArray.count + 1,多出来的那一行就是给开关行"留位置"。

相应地,库重写了cellForRowAtIndexPath:(SLExpandableTableView/SLExpandableTableView.m 第 436-460 行):当indexPath.row == 0且该分区可展开时,它不再询问你的数据源,而是直接返回你通过expandingCellForSection:提供的开关行,并根据当前状态把它摆成"已展开"或"已折叠"的样式。

🧠 三、核心拆解:numberOfRowsInSection 的折叠魔法

真正的魔术发生在库自己实现的这个回调里(SLExpandableTableView/SLExpandableTableView.m 第 415-434 行),逻辑浓缩后就这几行:

if ([self.myDataSource tableView:self canExpandSection:section]) { if ([self.myDataSource tableView:tableView numberOfRowsInSection:section] == 0) { return 0; // ① 空分区直接返回 0 } self.expandableSectionsDictionary[key] = @YES; if ([self.showingSectionsDictionary[key] boolValue]) { return [self.myDataSource tableView:tableView numberOfRowsInSection:section]; // ② 已展开:返回真实行数 } else { return 1; // ③ 未展开:只返回 1 行 } }

三步拆解:

  1. 先问你的数据源:这个分区能不能展开?不能就原样透传真实行数,完全不干预。
  2. 能展开则登记状态:把该分区记入expandableSectionsDictionary,供点击回调和单元格渲染时查询。
  3. 按状态"谎报"行数:如果该分区当前没有展开showingSectionsDictionary为 NO),就向 UITableView 报告"这个分区只有 1 行"——于是屏幕上只剩下开关行,其余多行被整体"藏"了起来。

这就是标题的答案:折叠不是隐藏单元格,而是让 UITableView 根本不知道那些行的存在。展开时把行数改回真实值,配合插入行动画,视觉上的"展开"就完成了。

为了支撑这套记账,库内部维护了 4 个状态字典(SLExpandableTableView/SLExpandableTableView.h 第 63-66 行):

字典作用
expandableSectionsDictionary记录哪些分区可展开(在 numberOfRowsInSection 回调中顺手写入)
showingSectionsDictionary记录分区当前是"展开"还是"折叠",是折叠判断的依据
downloadingSectionsDictionary记录哪些分区正在等待数据下载,用来在开关行上显示 loading
animatingSectionsDictionary记录哪些分区正在播放动画,用于区分普通的 willDisplayCell 回调

注意一个小细节:reloadData时库会清空这些状态(reloadDataAndResetExpansionStates:,第 337-339、150-154 行),全部回到折叠态——因为旧索引对应的状态已经不可信了。

🎬 四、点击后的展开与折叠动画(简析)

折叠成 1 行只是"静态"的一半,另一半是用户点击开关行后的动画处理(didSelectRowAtIndexPath:,第 385-411 行):

  • 点中的是可展开分区的 row 0:已展开则调collapseSection:,未展开则调expandSection:
  • 点中的是数据行(row > 0):原样转发给你自己的 delegate,业务逻辑不受影响。

collapseSection:(第 268-328 行)的动画做法:在beginUpdates/endUpdates事务里,把 row 1 到 row N-1 一次性deleteRowsAtIndexPaths:删掉,同时把开关行切回"折叠"样式;expandSection:(第 201-266 行)则是反过来insertRowsAtIndexPaths:。默认动画为UITableViewRowAnimationFadereloadAnimation,第 134 行)。

还有一个性能保险丝:maximumRowCountToStillUseAnimationWhileExpanding(默认NSIntegerMax)。当展开行数超过阈值时,库会放弃逐行动画,直接整体reloadData,避免大分区卡顿。

⚠️ 五、新手必看的 5 个坑

  1. 数据源行数必须 +1:可展开分区里要给开关行留位置,否则展开后的行数对不上,动画会崩。
  2. row 0 没有选择回调:头文件(第 78 行)明确说明,可展开分区的 row 0 不会再触发didSelectRowAtIndexPath:回调,不要在上面挂跳转逻辑。
  3. 别直接读tableView.dataSourcegetter:它返回的是库自己,要访问自己的数据源请用myDataSource(头文件第 82-84 行有专门注释)。
  4. 懒加载要闭环:声明需要下载数据后,成功后调用expandSection:animated:,失败调用cancelDownloadInSection:,否则开关行会一直停在 loading 态。
  5. reloadData 会重置折叠状态:刷新数据后所有分区回到折叠态,且"可展开"标记会被重新询问,属于预期行为。

📁 六、核心文件速查

文件说明
SLExpandableTableView/SLExpandableTableView.h对外接口:3 个数据源协议方法 + 展开/折叠公开方法
SLExpandableTableView/SLExpandableTableView.m核心实现:行数折叠、点击分发、展开/折叠动画
Tests/SLExpandableTableViewTests/SLExpandableTableViewController.m完整示例:含懒加载下载的分区列表演示
Tests/Podfile通过 CocoaPods 引入的依赖示例
SLExpandableTableView.podspec组件的 CocoaPods 发布配置

到这里,折叠"藏行"的原理已经讲透;展开动画的完整时序、下载态与状态字典的交互细节,留待源码解析(下)继续。

【免费下载链接】SLExpandableTableViewSLExpandableTableView is a UITableView subclass that gives you easy access to expandable and collapsable sections by just implementing a few more delegate and dataSource protocols. (iPhone, iPad, iOS)项目地址: https://gitcode.com/gh_mirrors/sl/SLExpandableTableView

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询