TableViewSectionedViewController - 多层级的列表页

TableViewSectionedViewController - 多层级的列表页 - 图1

演示如何使用 RxDataSources 来布局列表页,你可以在这里下载这个例子


简介

这是一个多层级列表页,它主要需要完成这些需求:

  • 每个 Section 显示对应的标题
  • 每个 Cell 显示对应的元素以及行号
  • 根据 CellindexPath 控制行高
  • Cell 被选中时,显示一个弹框

整体结构

TableViewSectionedViewController - 多层级的列表页 - 图2

以上这些需求,只需要一页代码就能完成:

  1. class SimpleTableViewExampleSectionedViewController
  2. : ViewController
  3. , UITableViewDelegate {
  4. @IBOutlet weak var tableView: UITableView!
  5. let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Double>>(
  6. configureCell: { (_, tv, indexPath, element) in
  7. let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
  8. cell.textLabel?.text = "\(element) @ row \(indexPath.row)"
  9. return cell
  10. },
  11. titleForHeaderInSection: { dataSource, sectionIndex in
  12. return dataSource[sectionIndex].model
  13. }
  14. )
  15. override func viewDidLoad() {
  16. super.viewDidLoad()
  17. let dataSource = self.dataSource
  18. let items = Observable.just([
  19. SectionModel(model: "First section", items: [
  20. 1.0,
  21. 2.0,
  22. 3.0
  23. ]),
  24. SectionModel(model: "Second section", items: [
  25. 1.0,
  26. 2.0,
  27. 3.0
  28. ]),
  29. SectionModel(model: "Third section", items: [
  30. 1.0,
  31. 2.0,
  32. 3.0
  33. ])
  34. ])
  35. items
  36. .bind(to: tableView.rx.items(dataSource: dataSource))
  37. .disposed(by: disposeBag)
  38. tableView.rx
  39. .itemSelected
  40. .map { indexPath in
  41. return (indexPath, dataSource[indexPath])
  42. }
  43. .subscribe(onNext: { pair in
  44. DefaultWireframe.presentAlert("Tapped `\(pair.1)` @ \(pair.0)")
  45. })
  46. .disposed(by: disposeBag)
  47. tableView.rx
  48. .setDelegate(self)
  49. .disposed(by: disposeBag)
  50. }
  51. // to prevent swipe to delete behavior
  52. func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
  53. return .none
  54. }
  55. func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  56. return 40
  57. }
  58. }

我们首先创建一个 dataSource: RxTableViewSectionedReloadDataSource<SectionModel<String, Double>>:

  1. let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Double>>(
  2. configureCell: { (_, tv, indexPath, element) in
  3. let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
  4. cell.textLabel?.text = "\(element) @ row \(indexPath.row)"
  5. return cell
  6. },
  7. titleForHeaderInSection: { dataSource, sectionIndex in
  8. return dataSource[sectionIndex].model
  9. }
  10. )

通过使用这个辅助类型,我们就不用执行数据源代理方法,而只需要提供必要的配置函数就可以布局列表页了。

第一个函数 configureCell 是用来配置 Cell 的显示,而这里的参数 element 就是 SectionModel<String, Double> 中的 Double

第二个函数 titleForHeaderInSection 是用来配置 Section 的标题,而 dataSource[sectionIndex].model 就是 SectionModel<String, Double> 中的 String

然后为列表页订制一个多层级的数据源 items: Observable<[SectionModel<String, Double>]>,用这个数据源来绑定列表页。

TableViewSectionedViewController - 多层级的列表页 - 图3

这里 SectionModel<String, Double> 中的 String 是用来显示 Section 的标题。而 Double 是用来绑定对应的 Cell。假如我们的列表页是用来显示通讯录的,并且通讯录通过首字母来分组。那么应该把数据定义为 SectionModel<String, Person>,然后用首字母 String 来显示 Section 标题,用联系人 Person 来显示对应的 Cell

由于 SectionModel<Section, ItemType> 是一个范型,所以我们可以用它来定义任意类型的 Section 以及 Item

最后:

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. ...
  4. tableView.rx
  5. .setDelegate(self)
  6. .disposed(by: disposeBag)
  7. }
  8. ...
  9. func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  10. return 40
  11. }

这个是用来控制行高的,tableView.rx.setDelegate(self)... 将自己设置成 tableView 的代理,通过 heightForHeaderInSection 方法提供行高。

参考