【Swift5】UITableViewのあるセルに設定した値が、他のセルにも意図せず再利用されてしまう【Xcode12】
事象自体の説明
以下画像の10と表示されている一番上のセルをスクロールすると、
別のセルの内容が反映されてしまっている事象です。
アプリの使用上、1行目(10と表示されていたセル)には番号が割り当てられないのが適しておりますが、スクロールして戻ってくるとセルの表示内容が不適切なものになっております。
対応策
結論から言うと、スクロールするレイアウト・スクロールしないレイアウト問わず、カスタムセルのクラス内にoverrideしてprepareForReuse()を書きます。
その後、全てのセルの描写をセルのデフォルト状態(元々の状態)に戻す処理を書きます。
以下のコードだと、UITableViewCellを継承して作成したカスタムクラスのすべてのオブジェクト(わかりやすくいうとパーツ)をデフォルト状態(元々の状態)に戻しております。
override func prepareForReuse() {
super.prepareForReuse()
// TableViewのセルを再利用される時に以前の値が入らないようにクリアする
self.numberLabel?.text = ""
self.damageLabel?.text = ""
self.poisonLabel.isHidden = true
self.fireLabel.isHidden = true
self.recoveryButton.isHidden = true
self.recoveryImageView.isHidden = true
self.poisonImageView.isHidden = true
self.settingsButton.isHidden = true
}
prepareForReuse()はセルが再利用される際に呼び出せる処理です。
こちらで再利用時のみ、セルを前のセルのデータが残っている状態からクリアしてあげています。
tableView:cellForRowAtIndexPath:にリセット処理を書くのはダメか?
実際は処理として同一の内容を返しますが、ベストプラクティスではないように思えます。
推奨しない理由としては、tableView:cellForRowAtIndexPath:に書いてしまうと、その画面を1番最初に表示する際やスクロールを行なっていない状態でさえクリアする処理が入ってしまいます。
それは描画上必要な処理でしょうか。
あくまで再利用する際に、セルの状態をデフォルトに戻して、正しく描画したいので、カスタムセルのクラス内でprepareForReuse()でのデフォルト状に戻すやり方を推奨しています。
上記の推奨される書き方をふまえた上での設計について
設計というと大袈裟ですが、tableView:cellForRowAtIndexPath:でセルを描画する際には表示するセルがデフォルトの状態であることを想定して記載した方が良いです。
普段表示しないけど特定状況でのみ表示するオブジェクトなどは、セル側のxibファイルなどでisHidden = trueを有効にして、何も操作しなくてもデフォルト状態になるようにxibも設定してあげるといいです。
追記
本記事投稿後にはなりますが、
あきおさんがこのような動画をアップロードされていました。
視聴しましたが、tableView:cellForRowAtIndexPath:にてセルの状態をデフォルト状態に戻しておりました。
しかしながら、やはり記事内で述べたように再利用する際に以前の表示内容が問題になるので、セルの再利用時だけに呼び出されるprepareForReuseでセルの状態をデフォルト状態にしてあげる方が適切な気もします。
この辺り、実際どうなんでしょうね。
詳しい方いたらぜひご教示ください。