RxDataSourceを使ってTableViewのHeaderとFooterを生成してみる
概要
前回はRxDataSourceを使って基本的なUITableViewのテンプレートを作ってみました。
RxDataSource
今回はこのRxDataSourceについて弄ってみてどんな事ができるのかについてより詳しくみていこうと思います。
基本的なstoryboardの配置やソースコードの配置は前回のままですので念の為コピーしたものを載せておきます。
Main.storyboard
ViewController.swift
import UIKit import RxSwift import RxCocoa import RxDataSources /// TableViewCellに紐付けるDataModel struct CustomCellModel { var name: String var email: String } /// セクションヘッダーの名前とセクション内のitem struct SectionOfCustomData { /// セクションヘッダーの名前 var header: String /// indexPath.row のcellのデータ var items: [Item] } /// RxDataSourceを使ってDataModelとdataSourceを紐づけるため extension SectionOfCustomData: SectionModelType { // Item に CellのDataModelを紐づける typealias Item = CustomCellModel // ほぼテンプレで可能 init(original: SectionOfCustomData, items: [Item]) { self = original self.items = items } } class ViewController: UIViewController { @IBOutlet weak var tableView: UITableView! let disposeBag = DisposeBag() let dataSource = RxTableViewSectionedReloadDataSource<SectionOfCustomData>( configureCell: { dataSource, tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = item.name return cell }) // 複数のsectionを表示させたい場合はSectionOfCustomDataを生成していきます。 // SectionOfCustomData の let sections = [ SectionOfCustomData(header: "1st section", items: [CustomCellModel(name: "山田花子", email: "hanako@gmail.com"), CustomCellModel(name: "田中太郎", email: "taro@gmail.com"), CustomCellModel(name: "石田真一", email: "shinichi@gmail.com")]), ] override func viewDidLoad() { super.viewDidLoad() dataSource.titleForHeaderInSection = { dataSource, index in return dataSource.sectionModels[index].header } Observable.just(sections) // sectionsを生成して .bind(to: tableView.rx.items(dataSource: dataSource)) // itemsのdataSourceに紐づける .disposed(by: disposeBag) } }
これが前回までのソースコードとなります。
今回はこれの変更を加えます。
HeaderViewの追加
試しにHeaderViewを追加してみます。RxDataSourceのライブラリのReadmeを確認すると
RxDataSource.swift
/// HeaderViewの作成 dataSource.titleForHeaderInSection = { dataSource, index in return dataSource.sectionModels[index].header } /// FooterViewの作成 dataSource.titleForFooterInSection = { dataSource, indexPath in return dataSource.sectionModels[index].footer }
このようになっています。
そのため、viewDidLoad()
のコードを次のように修正してみます。
ViewController.swift
override func viewDidLoad() { super.viewDidLoad() // HeaderViewを追加する dataSource.titleForHeaderInSection = { dataSource, index in return dataSource.sectionModels[index].header } Observable.just(sections) // sectionsを生成して .bind(to: tableView.rx.items(dataSource: dataSource)) // itemsのdataSourceに紐づける .disposed(by: disposeBag) }
このように修正してアプリをビルドしてみます。
このように無事にヘッダーが表示されたら成功です。
FooterViewの追加
では、次にCustomなFooterViewの追加を実装してみます。
まずはSectionOfCustomData
を次のように修正してみます。
ViewController.swift
/// セクションヘッダーの名前とセクション内のitem struct SectionOfCustomData { /// セクションヘッダーの名前 var header: String /// indexPath.row のcellのデータ var items: [Item] /// セクションフッターの名前 var footer: String }
セクションフッターの名前を追加しました。
これによって変数sectionsの修正が必要になります。
ViewController.swift
// 複数のsectionを表示させたい場合はSectionOfCustomDataを生成していきます。 let sections = [ SectionOfCustomData(header: "1st section", items: [CustomCellModel(name: "山田花子", email: "hanako@gmail.com"), CustomCellModel(name: "田中太郎", email: "taro@gmail.com"), CustomCellModel(name: "石田真一", email: "shinichi@gmail.com")], footer: "1st section footer"), // footer: を追加しました ]
これによってsectionsのデータ構造が変わってfooterも追加されます。
ですが、これだけではUITableViewには反映されません。
最後にviewDidLoad()
をこのように修正します。
ViewController.swift
override func viewDidLoad() { super.viewDidLoad() // HeaderViewを追加する dataSource.titleForHeaderInSection = { dataSource, index in return dataSource.sectionModels[index].header } // FooterViewを追加する dataSource.titleForFooterInSection = { dataSource, index in return dataSource.sectionModels[index].footer } Observable.just(sections) // sectionsを生成して .bind(to: tableView.rx.items(dataSource: dataSource)) // itemsのdataSourceに紐づける .disposed(by: disposeBag) }
これでアプリをビルドしてみましょう。
次のようにfooterが追加表示されていたら成功です。
Footerが表示されるようになりました。
このようにRxDataSourceを上手く使えば普通のUITableViewのようにデータを扱えるようになります。
Qiitaの記事ではRxDataSourceの使い方よりもそれを意識した設計方針の方が難しく感じますね。
RxDataSourceに苦手意識を持っている僕のような方であれば今回の記事を参考にして最低限の実装方法を理解すれば自ずと 設計方針が見えてくると思います。
とりあえず、これでUITableViewの実装は一通り経験できたので頑張れそうだと思います。
全体のソースコード
一応、念の為、全体のソースコードを乗せておきます。
ViewController.swift
import UIKit import RxSwift import RxCocoa import RxDataSources /// TableViewCellに紐付けるDataModel struct CustomCellModel { var name: String var email: String } /// セクションヘッダーの名前とセクション内のitem struct SectionOfCustomData { /// セクションヘッダーの名前 var header: String /// indexPath.row のcellのデータ var items: [Item] /// セクションフッターの名前 var footer: String } /// RxDataSourceを使ってDataModelとdataSourceを紐づけるため extension SectionOfCustomData: SectionModelType { // Item に CellのDataModelを紐づける typealias Item = CustomCellModel // ほぼテンプレで可能 init(original: SectionOfCustomData, items: [Item]) { self = original self.items = items } } class ViewController: UIViewController { @IBOutlet weak var tableView: UITableView! let disposeBag = DisposeBag() let dataSource = RxTableViewSectionedReloadDataSource<SectionOfCustomData>( configureCell: { dataSource, tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = item.name return cell }) // 複数のsectionを表示させたい場合はSectionOfCustomDataを生成していきます。 let sections = [ SectionOfCustomData(header: "1st section", items: [CustomCellModel(name: "山田花子", email: "hanako@gmail.com"), CustomCellModel(name: "田中太郎", email: "taro@gmail.com"), CustomCellModel(name: "石田真一", email: "shinichi@gmail.com")], footer: "1st section footer"), ] override func viewDidLoad() { super.viewDidLoad() // HeaderViewを追加する dataSource.titleForHeaderInSection = { dataSource, index in return dataSource.sectionModels[index].header } // FooterViewを追加する dataSource.titleForFooterInSection = { dataSource, index in return dataSource.sectionModels[index].footer } Observable.just(sections) // sectionsを生成して .bind(to: tableView.rx.items(dataSource: dataSource)) // itemsのdataSourceに紐づける .disposed(by: disposeBag) } }
なんやかんやでちょっとずつ複雑になってきましたね。