はじめに
UITableViewの更新手法は2種類あります。
- 全更新
reloadData()
- 差分更新
insertRows(at:with:)
,deleteRows(at:with:)
,moveRow(at:to:)
,reloadRows(at:with:)
差分更新は更新時にアニメーションが付くので好きです。
今回は、差分更新に挑戦する人が詰まりやすそうなエラーを解説しようと思います。
説明に使ったコードの完全なコードが見たい方はこちらから確認できます。
https://github.com/Se1getsu/diff-update-test
先にデータを更新しよう
差分更新でセルの追加・削除を行うときです。
先にデータを更新してから、TableViewを更新しましょう! 逆でやるとクラッシュします!
エラーの出方に少し癖があるので、それぞれの場合について見ていきます。
セル追加時に出るエラー
dataの内容をtableViewが表示しています。
class ViewController: UIViewController, UITableViewDataSource {
private let tableView: UITableView = ...
private var data = ["zero", "one"]
以下のように書けば、ボタンを押すとone
の下にtwo
が追加されるようにできます。
// 正解
@objc func didTapButton() {
data.append("two") // [zero, one] -> [zero, one, two]
tableView.insertRows(at: [IndexPath(row: 2, section: 0)], with: .automatic)
}
それでは、2つの手続きを以下のように、逆にするとどうでしょう?
// 間違い
@objc func didTapButton() {
tableView.insertRows(at: [IndexPath(row: 2, section: 0)], with: .automatic)
data.append("two") // [zero, one] -> [zero, one, two]
}
ボタンを押すと、AppDelegate.swift
にエラーが出てクラッシュします。
Thread 1: "attempt to insert row 2 into section 0, but there are only 2 rows in section 0 after the update"
reloadData()
なら cellForRowAt に Index out of range が出るので分かりやすいですが、差分更新ではこのようにAppDelegate.swift
にエラーが出るので割と分かりにくいです。
セル削除時に出るエラー
以下のようにすれば、ボタンを押すとone
が削除されるようにできます。
// 正解
@objc func didTapButton() {
data.removeLast() // [zero, one] -> [zero]
tableView.deleteRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
}
これも逆で書いてみます。
// 間違い
@objc func didTapButton() {
tableView.deleteRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
data.removeLast() // [zero, one] -> [zero]
}
ボタンを押すと、AppDelegate.swift
にエラーが出てクラッシュします。
Thread 1: "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). Table view: <UITableView: ...>"
なぜエラーが出るのか
エラー文を読めば分かりますが、要するにこういうことです。
insertRows(at:with:)
やdeleteRows(at:with:)
を使ってセルの数を変化させる時は、変化後のセルの数が、dataSourceのtableView(_:numberOfRowsInSection:) -> Int
が返すセルの数と、一致していなければならない ということです。
他のパターン
データは2個削除したのに、1個しか削除してない時などもエラーになります。
@objc func didTapButton() {
data = [] // [zero, one] -> []
tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
}
Thread 1: "Invalid batch updates detected: the number of sections and/or rows returned by the data source before and after performing the batch updates are inconsistent with the updates.\nData source before updates = { 1 section with row counts: [2] }\nData source after updates = { 1 section with row counts: [0] }\nUpdates = [\n\tDelete row (0 - 0)\n]\nTable view: <UITableView: ...>"
参考文献