まだ情報が少なく公式のドキュメントを読んでもわかりずらかったので共有しておく。
# 環境
swift 3.0
Xcode version 8.0
SwiftBond/Bond ~> 5.0
Observable2DArrayとは
Bondを利用していてUITableViewなどのデータを複数のセクションに分けたい場合に使用するデータモデル。
一つのテーブルにセクションが一つしかない場合は無理して使う必要はなくObservableArrayでも十分。
使い方
データサイド
まずセクションごとのデータを
let cities = Observable2DArraySection<SectionMetadata, String>(
metadata: (header: "Cities", footer: "That's it"),
items: ["Paris", "Berlin"]
)
のように定義する。metadataは
typealias SectionMetadata = (header: String, footer: String)
のように定義しておく。headerのタイトルだけしか必要なければStringで
let cities = Observable2DArraySection<String, String>(
metadata: "Cities",
items: ["Paris", "Berlin"]
)
でも良い。そしてObservable2DArraySectionの集合が
let array = MutableObservable2DArray([cities])
となる。
データを追加したい場合は
array.appendItem("Copenhagen", toSection: 0)
などのObservable2DArraySectionクラスのメソッドを利用する。
UIサイド
struct MyBond: TableViewBond {
func cellForRow(at indexPath: IndexPath, tableView: UITableView, dataSource: Observable2DArray<SectionMetadata, String>) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = array[indexPath]
return cell
}
func titleForHeader(in section: Int, dataSource: Observable2DArray<SectionMetadata, String>) -> String? {
return dataSource[section].metadata.header
}
func titleForFooter(in section: Int, dataSource: Observable2DArray<SectionMetadata, String>) -> String? {
return dataSource[section].metadata.footer
}
}
なんとなく見覚えのあるメソッドだが、これをTableViewControllerとは別に(ここではMyBondとして)定義しておく。
この結果TableViewのクラスではviewDidLoadあたりで、
array.bind(to: tableView, using: MyBond())
を呼ぶだけで
var numberOfSections: Int
func numberOfRows(inSection: Int)
func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
などのUITableViewのdelegateを定義しなくても済む。
さらにここでは説明しないがSignalを利用することにより、
tableView.selectedRow.observeNext { row in
print("Tapped row at index \(row).")
}.disposeIn(bnd_bag)
などと書けるようになる。
はまったところ
itemにカスタムモデルを使う場合
上記だと単純に["Paris", "Berlin"]となっているところを
struct City {
let name: String
}
のようなモデルの配列にしたい場合。
let array = MutableObservable2DArray(
[
Observable2DArraySection<SectionMetadata, City>(
metadata: (header: "Cities", footer: "That's it"),
items: [
City(name: "Paris"),
City(name: "Berlin"),
]
)
]
)
とした上で、MyBondないのObservable2DArray<SectionMetadata, String>をObservable2DArray<SectionMetadata, City>としてやればよさそうだが、それだとtitleForHeaderやtitleForFooterでエラーになる。
解決方法は
class MyBond: TableViewBond {
typealias DataSource = Observable2DArray<SectionMetadata, City>
// 略
}
とDataSourceという名前に紐付る必要があった。
MutableObservable2DArrayをクラス間でどうやって共有するか
struct ViewModel {
let array = MutableObservable2DArray(
[
Observable2DArraySection<SectionMetadata, City>(
metadata: (header: "Cities", footer: "That's it"),
items: [
City(name: "Paris"),
City(name: "Berlin"),
]
)
]
)
}
としてUI側からは
class TableViewController: UITableViewController {
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.array
.bind(to: tableView, using: MyBond())
.disposeIn(bnd_bag)
}
}
class MyBond: TableViewBond {
typealias DataSource = Observable2DArray<SectionMetadata, City>
let viewModel = ViewModel()
func cellForRow(at indexPath: IndexPath, tableView: UITableView, dataSource: DataSource) -> UITableViewCell {
let city = viewModel.array[indexPath]
// 略
}
}
とすることでMutableObservable2DArrayの内容をTableに反映させることができた、ただ、動的にデータを追加したりすると途端にクラッシュしてしまう。
気づいてみれば当然だけど、この場合ViewModelをシングルトン化してあげなければいけない。
実装例
https://gist.github.com/ofl/dd005c1568681ff09423900354f075e8
使ってみて
Bondを1年近く使っているのだが、ReactiveKitとの統合のためか書き方が何度目かの大掛かりな変更となっている(のでこれから使い始める人は情報が少ないので大変)。
ただObservable2DArraySectionについては以前のMutableArrayを多段にする書き方よりは好ましくなったと思う。