あるときこんなレビュー指摘がありました。
「CellのViewをhiddenにしているけど、ずっとhiddenになったままでいいの?再利用されるときに想定外の場合になる可能性があるよ」
最初、??と思ったのですが、そのときにTableViewCellは再利用されていることを知りました ※結構前の話です
今回はそんなセルの再利用についてまとめてみようと思います。
Cellの再利用
Cellを使う場面で真っ先に思いつくのはTableViewだと思います
TableViewのセルが大量にある場合にはViewの描画数が非常に多くなり、特にスクロール時には、パフォーマンスの低下が懸念されます
そのため、Cellの再利用で毎回新しくViewを作るのではなく以前に生成したcellを利用することで、メモリ割り当てを最小限にします
ただし、Cellの見た目を変更した際にリサイクル前のCellに対する変更が残ってしまうという問題がしばしば起こります。 例えばカスタムセルクラスの中で、データの中に画像が存在しない場合は、Cellに設置したimageViewを非表示にする処理を書いたとします。 そして、imageViewが非表示となったCellを再利用する時、画像の有無にかかわらずimageViewは非表示のままで表示されるので、注意が必要になります
Cell再利用の流れ
Cellを再利用するために、まず事前にregister(_:forCellReuseIdentifier:)
を呼んで、ViewをCellのテンプレートとして登録します。
テンプレートはユニークなreuseIdentifier
によって管理されます
次に、Cellの生成(参照)時、つまりfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
メソッドが呼ばれます。
このメソッドは、再利用可能なCellがあればそれを、なければ新しく作成したCellを返します。再利用可能なCellは、reuseIdentifierに紐づけられたreuse queue
に格納されています
tableviewは裏側でreuse queueという、データの箱のようなものを持っており、reuseIdentifierごとにreuse queueが存在します。 画面外に出たCellは、自身のidentifierに紐づいたreuse queueに追加されます。 そして、同じidentifierのCellが表示されようとする時、queueから取り出されます。 次の図のようにスクロールしたら次のcellがqueueから取り出されるイメージです
Cell再利用で想定外の表示にならないようにするために
ここからはCell再利用で想定外の表示にならないためにやるべきデータ初期化のタイミングについて2つ紹介します。2つともやることは同じです
まずはfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
で行うパターンです
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellidendifier", for: indexPath) // 画像を初期化 cell.imageView.image = nil cell.imageView.image = UIImage(named: "test") return cell }
imageViewのimageを初期化した上で画像を設定しています。この方法だとfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
に多く初期化する処理を書く可能性があり、微妙なところです
もう1つはカスタムセルのprepareForReuse()
で初期化を行うです
prepareForReuse()は再利用可能なセルを準備するときに呼ばれます。このタイミングで初期化を行います
class CustomTableViewCell: UITableViewCell { @IBOutlet private weak var label: UILabel! // セルのリサイクル対策での初期化処理を行う override func prepareForReuse() { super.prepareForReuse() } func configure(text: String) { label.text = text } }
上記のようにするとconfigureメソッドやオートレイアウトを使う場合にはawakeFromNib()
で初期化を行う必要がないです