yashigani?.days

週刊少年ジャンプについてだらだら書きます

UITableViewでレイアウトの違うセルをどう扱うか #関モバ

毎月恒例の関モバ #8に参加しました.

kanmoba.connpass.com

今回は,UITableView複数のレイアウトを持つセルをどう扱うかというトークをしました.

speakerdeck.com

骨子としては,UITableView複数のレイアウトを持つセルを実装するとき,いくつかの方法がありますがコードでレイアウトをいじるとAuto Layoutの指定が複雑になるので,レイアウトのパターンごとにxibを分けるといいというものです.

複数のxibを使うことについて少し補足

UITableViewにおいてxibを増やす場合,xibの登録やセルのリユースなどセルを使うための決め事が複雑になることが問題になります. セルを複数のView Controllerで使いまわしたい場合,それぞれで同じ実装をすることが強いられます. そこで,このトークでは実装パターンとしてEntryCellRepresentableのようなprotocolを使うことを紹介しました. 紹介したものは単純で以下のような実装です. ※ この例ではEntryCellに対して複数のxib(EntryCell.xibとEntryCellWithImage.xibの2つ)が存在し,それぞれでレイアウトを定義することを前提としています(画像があるパターンと無いパターンです).

protocol EntryCellRepresentable {
  var tableView: UITableView! { get }
  func registerEntryCell()
  func dequeueEntryCell(entry: Entry, forIndexPath indexPath: NSIndexPath) -> EntryCell
}

extension EntryCellRepresentable {
  func registerEntryCell() {
    ["EntryCell", "EntryCellWithImage"].forEach {
      tableView.registerNib(UINib(nibName: $0, bundle: nil), forCellReuseIdentifier: $0)
    }
  }

  func dequeueEntryCell(entry: Entry, forIndexPath indexPath: NSIndexPath) -> EntryCell {
    let reuseIdentifier = entry.hasImage ? "EntryCellWithImage" : "EntryCell"
    let cell = tableView.dequeueCellReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! EntryCell
    cell.entry = entry
    return cell
  }
}

EntryCellRepresentableEntryCellを使ううえでの規約(全てのパターンのxibを漏れなくregisterNibする,dequeueCellReusableCellWithIdentifierし,モデルであるEntryをセットする)を隠蔽します. UITableViewを使うとき,多くはUITableViewControllerのサブクラスを使うことでしょう. UITableViewControllerはプロパティにvar tableView: UITableView!を持っていますので,EntryCellRepresentableを指定するだけでprotocolに適合させることができます. したがって,EntryCellを表示するclass EntriesViewController: UITableViewControllerはこのように実装できます.

class EntriesViewController: UITableViewController, EntryCellRepresentable {
  override func viewDidLoad() {
    super.viewDidLoad()
    registerEntryCell()
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let entry = entries[indexPath.row]
    return dequeueEntryCell(entry, forIndexPath: indexPath)
  }
}

UITableViewを自分で追加する場合はvar tableView: UITableView!を実装するだけです. このような工夫によって,使いまわしやすさを維持しつつxibを分けることが可能です.