なんとかするから、なんとかなる

エンジニア関係のことを書きます

iOS UITableViewのdeleteRows(at:with:)で落ちる

English version is below.

はじめに

UITableViewを使っていて、deleteRow(at:with:) を読んだときにクラッシュする問題にぶつかりました。

その解決方法について紹介します。

問題

次のようなコードを書いたときにアプリがクラッシュしました。

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    guard editingStyle == .delete else { return }

    self.tableView.deleteRows(at: [indexPath], with: .fade) // クラッシュ
    
    // myArrayはデータソースです。
    self.myArray.remove(at: indexPath.row)

エラーメッセージは次の通り。

 *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.54.4/UITableView.m:2012

解決方法

UITableViewのdeleteRow(at:with:)はデータソースを変更する前に読んではいけません。 したがって、次のようなコードが正解です。

なので、たまにデータソースの削除に失敗することがある場合、注意が必要です。 何かしらの方法で削除が失敗したことを通知する必要があります。

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    guard editingStyle == .delete else { return }

    // myArrayはデータソースです。
    self.myArray.remove(at: indexPath.row)

    self.tableView.deleteRows(at: [indexPath], with: .fade) // データソース変更後にUITableViewを削除する操作

その他

UITableViewのdeleteRow(at:with:)を呼ぶときに、beginUpdates() と endUpdates()を呼ぶ必要がありそうです。

しかし今回の場合では必要ありません。

公式ドキュメントによると、これらのメソッドはアニメーションブロック内でdeleteRow(at:with:)を呼ぶときに必要なようです。

参考URL

https://developer.apple.com/documentation/uikit/uitableview/1614960-deleterows