多分これが一番楽だと思います
UITableViewとは
- めちゃくちゃ使うパーツ
- Cellを入れ替えられるので便利
- 状態によって、indexPathやCellTypeを出し分けてると徐々に訳が解らなくなる
毎日使うモノだからこそ、楽にしたいですね。
楽とは
- 速い
- かんたん
- 誰でもできる
- 変更しやすい(重要)
備考
https://qiita.com/tattn/items/dc7dfe2fceec00bb4ff7
こちらのUITableView+.swiftを使わせていただいております
ありがとうございます。
1.シンプルなコード
import UIKit
class ThirdViewController: UIViewController {
@IBOutlet weak var hogeTableView: UITableView!
enum CellType {
case aaa
case bbb
case ccc
}
let cellTypes:[CellType] = [.aaa,
.bbb,
.ccc]
override func viewDidLoad() {
super.viewDidLoad()
hogeTableView.delegate = self
hogeTableView.dataSource = self
hogeTableView.rowHeight = UITableViewAutomaticDimension
}
}
extension ThirdViewController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellTypes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = cellTypes[indexPath.row]
switch cellType {
case .aaa:
let cell = tableView.dequeueReusableCell(with: AAATableViewCell.self, for: indexPath)
return cell
case .bbb:
let cell = tableView.dequeueReusableCell(with: BBBTableViewCell.self, for: indexPath)
return cell
case .ccc:
let cell = tableView.dequeueReusableCell(with: CCCTableViewCell.self, for: indexPath)
return cell
}
}
}
結果
解説
UITableViewが煩雑になるのはindexPathという位置に応じたcellを場合分けで出そうとするからです。
なので、cellのtypeによって以下のように返却できると非常にシンプルになります。
当たり前ですがCellは位置に依存しないように定義するべきです。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = cellTypes[indexPath.row]
switch cellType {
case .aaa:
let cell = tableView.dequeueReusableCell(with: AAATableViewCell.self, for: indexPath)
return cell
case .bbb:
let cell = tableView.dequeueReusableCell(with: BBBTableViewCell.self, for: indexPath)
return cell
case .ccc:
let cell = tableView.dequeueReusableCell(with: CCCTableViewCell.self, for: indexPath)
return cell
}
}
CellTypeをどのように定義するかは、Swiftになって大分書きやすくなりました。
実際にUITableViewがどのような順でcellを表示するかは、cellTypesで定義してやります。
enum CellType {
case aaa
case bbb
case ccc
}
let cellTypes:[CellType] = [.aaa,
.bbb,
.ccc]
ちなみに高さは最近AutoLayout任せなことが多いです(重い時以外)
この方法が楽かどうかは人によるかもしれません。
hogeTableView.rowHeight = UITableViewAutomaticDimension
2.cellの出し分けをする
ここまでなら、この書き方の恩恵をまだ感じないかもしれません。
次は、UserTypeにより2パターンの画面構成が考えられる場合です。
import UIKit
class SecondViewController: UIViewController {
@IBOutlet weak var hogeTableView: UITableView!
enum CellType {
case aaa
case bbb
case ccc
}
enum UserType {
case normal
case premium
}
var userType:UserType = .normal
func cellTypes() -> [CellType] {
switch userType {
case .normal:
return [.aaa,.bbb,.bbb,.bbb]
case .premium:
return [.aaa,.bbb,.ccc,.ccc]
}
}
override func viewDidLoad() {
super.viewDidLoad()
hogeTableView.delegate = self
hogeTableView.dataSource = self
hogeTableView.rowHeight = UITableViewAutomaticDimension
}
}
extension SecondViewController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellTypes().count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = cellTypes()[indexPath.row]
switch cellType {
case .aaa:
let cell = tableView.dequeueReusableCell(with: AAATableViewCell.self, for: indexPath)
return cell
case .bbb:
let cell = tableView.dequeueReusableCell(with: BBBTableViewCell.self, for: indexPath)
return cell
case .ccc:
let cell = tableView.dequeueReusableCell(with: CCCTableViewCell.self, for: indexPath)
return cell
}
}
}
結果
userTyle = .normal
userTyle = .premium
解説
enum UserType {
case normal
case premium
}
var userType:UserType = .normal
func cellTypes() -> [CellType] {
switch userType {
case .normal:
return [.aaa,.bbb,.bbb,.bbb]
case .premium:
return [.aaa,.bbb,.ccc,.ccc]
}
}
cellTypes()を関数にすることで、出し分けを行っています。
この方法は非常に強力です。画面内に何を表示するか、しないのかの条件分岐が多い場合は見通しが良くなります。複雑怪奇な条件や、1Cell毎に出す出さないの判定も、CellTypeを配列に追加していくだけなので楽です。
cellを返している関数の中身が先程とほとんど変わっていないところがみそです。
普通にやると、この中で複雑な条件分岐が発生します。
もちろん、画面操作の途中でuserTypeを変更することも可能です。
画面のモードによって、cellの生成部が荒らされることはありません(heightを返す関数でも同様)
3.sectionが複数の場合
さすがにsectionの管理が入ってくると若干複雑です。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var hogeTableView: UITableView!
enum SectionType {
case section1
case section2
case section3
}
enum CellType {
case aaa
case bbb
case ccc
}
let sectionTypes:[SectionType] = [.section1,
.section2,
.section3]
let cellTypes:[SectionType:[CellType]] = [.section1:[.aaa,.aaa,.bbb],
.section2:[.aaa,.ccc],
.section3:[.bbb,.ccc,.ccc]]
override func viewDidLoad() {
super.viewDidLoad()
hogeTableView.delegate = self
hogeTableView.dataSource = self
hogeTableView.rowHeight = UITableViewAutomaticDimension
}
func cellData(_ indexPath:IndexPath) -> CellData {
let data = CellData()
data.title = "section:\(indexPath.section) row:\(indexPath.row)"
return data
}
}
extension ViewController: UITableViewDelegate,UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionTypes.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellTypes[sectionTypes[section]]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = cellTypes[sectionTypes[indexPath.section]]![indexPath.row]
let data = cellData(indexPath)
switch cellType {
case .aaa:
let cell = tableView.dequeueReusableCell(with: AAATableViewCell.self, for: indexPath)
cell.setup(cellData: data)
return cell
case .bbb:
let cell = tableView.dequeueReusableCell(with: BBBTableViewCell.self, for: indexPath)
cell.setup(cellData: data)
return cell
case .ccc:
let cell = tableView.dequeueReusableCell(with: CCCTableViewCell.self, for: indexPath)
cell.setup(cellData: data)
return cell
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableCell(withIdentifier: "SectionHeaderTableViewCell")
return header
}
}
class CellData:NSObject {
var title:String = ""
}
結果
解説
しかしやってることは変わりません。
cellTypesをSectionTypeと[CellType]のDictionaryにしました。
cell供給部分は基本的に変化していません。
enum SectionType {
case section1
case section2
case section3
}
enum CellType {
case aaa
case bbb
case ccc
}
let sectionTypes:[SectionType] = [.section1,
.section2,
.section3]
let cellTypes:[SectionType:[CellType]] = [.section1:[.aaa,.aaa,.bbb],
.section2:[.aaa,.ccc],
.section3:[.bbb,.ccc,.ccc]]
もちろん実際に使用する時は、cellTypesが関数になることがあると思います。
ところで、同じCellTypeでも、Cell毎に表示するコンテンツは異なるはずです。
なので、indexPathに応じたデータモデルを受け取る関数を作りましょう。
func cellData(_ indexPath:IndexPath) -> CellData {
let data = CellData()
data.title = "section:\(indexPath.section) row:\(indexPath.row)"
return data
}
すると、cell供給部はシンプルに保てます。
もちろんcell側で解釈することになるわけで、今持ってるデータとCellに適したデータセットが異なる場合は、ViewModelを作ることになるかもしれません。
しかし大体の場合は、画面で持っているデータモデルがそのまま使用できる事が多いと思います。
case .aaa:
let cell = tableView.dequeueReusableCell(with: AAATableViewCell.self, for: indexPath)
cell.setup(cellData: data)
return cell
考える流れはこうです。
1.表示するデータのセットがある
2.各cellをどのように表示するか、形式を定義する(CellType)
3.cellを表示する順番を定義する
4.indexPathに応じたデータを受け取る
5.cell供給部は、「このcellの時はこれを返す」だけに集中する(実際にそのcellが要求されるかは考えない)
おわりに
今まで色んな書き方を見てきましたが、最近はこの書き方に落ち着きました。
この書き方ではsectionの数やcellの数を数える必要も、マジックナンバーを書く必要もありません。なので追加も分割も容易です。
CellはCellで定義して、TableViewはどれを出すか選ぶだけなので、安心してハードな場合分けもできます。
あと簡単です。Swift歴1ヶ月でも理解できると思います。
お試しあれ。