UITableViewDiffableDataSource

iOS13 から登場したUITableViewのセルを更新する手法です。今までよりも簡単かつ安全に更新することが可能になりました。

1. セクション情報とデータを Hashable で定義する

セクション情報とセルに渡すデータモデルは一意に識別できるように Hashable に準拠している必要があります。Swift の enum は Hashable に準拠しているのでセクション情報は enum で、データモデルは手動で Hashable に準拠させましょう。

セクション情報

enum Section: CaseIterable {
    case section1
    case section2
}

データモデル
今回は identifier プロパティを1つだけ持った Item というモデルを利用してみます。

struct Item: Hashable {
   let identifier = UUID()

   func hash(into hasher: inout Hasher) {
       hasher.combine(identifier)
   }

   static func == (lhs: Item, rhs: Item) -> Bool {
       return lhs.identifier == rhs.identifier
   }
}
※ Hashable についての公式ドキュメントはこちら

2. DataSource を定義する

セルの生成や、セル更新時のアニメーション手法などを定義します。

class SampleViewController: UITableViewController {
    private lazy var dataSource: UITableViewDiffableDataSource<Section, Item> = {
       let dataSource = UITableViewDiffableDataSource<Section, Item>(tableView: tableView) { (tableView, indexPath, item) -> UITableViewCell? in
           let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCell", for: indexPath)
           cell.textLabel?.text = "\(item.identifier)"
           return cell
       }
       
       // アニメーションを指定
       dataSource.defaultRowAnimation = .fade
       
       return dataSource
   }()
}

上記のコードを見てわかる通り、セルに表示するデータ(Item)は DataSourceの引数で渡ってくるようになり、今までのように indexPath を参照してセルにデータを渡す必要がなくなりました。

3. Snapshot を定義する

Snapshot は、テーブルセクション情報やセルに渡すデータを管理するクラスです。データをどのセクションに適用するかを指定します。updateUI というメソッドを定義してまとめてみました。

func updateUI() {
   var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
   snapshot.appendSections(Section.allCases)
   snapshot.appendItems([Item()], toSection: .section1)
   snapshot.appendItems([Item()], toSection: .section2)
   
   dataSource.apply(snapshot)
}

apply で実際にテーブルの更新処理を走らせます。apply は以下の2つの引数を定義することもできます。

animatingDifferences: セルを更新するときにアニメーションさせるかどうか
completion: アニメーションが完了した後に呼ばれるコールバック

4. TableView に DataSource をセットする

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.dataSource = dataSource   
}

あとは更新のタイミングで updateUI() を呼ぶだけです。

注意点として、canEditRowAt、commit:forRowAt、canMoveRowAt、moveRowAt は以下のように UITableViewDiffableDataSource のサブクラスを作成してそこに定義しないと動かないようなのでご留意ください。

class DataSource: UITableViewDiffableDataSource<Section, Item> {
   override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
       return true
   }
}
class SampleTableViewController: UITableViewController {
   private lazy var dataSource: DataSource = {
       let dataSource = DataSource(tableView: tableView) { (tableView, indexPath, item) -> UITableViewCell? in
           ...
       }
       
       ...
   }()
}



この記事が気に入ったらサポートをしてみませんか?