Hahnah Chronicle

[Swift] UICollectionView でスクロールすると Cell の配置が変わる問題の対処

Authors
原井 夏樹
Published on
Updated on

UICollectionViewCell のチェックマークの配置がスクロールで変わる問題を解決する方法を紹介する。

問題

まえの記事 の実装で、スクロールするほど Cell が多い場合、 スクロールをすることで Cell の配置が変わってしまうという問題が実は発生する。

問題のキャプチャ

修正方法を探る

CollectoinViewController のcollectionView(_:cellForItemAt:)メソッドについて挙動を確認してみて分かったことは、

  • indexPathの Cell が表示されるときに呼ばれる (作成時に呼ばれるものと勘違いしていた)
  • dequeueReusableCellメソッドで 再利用可能な Cell を取している (そのままやんけ)

↓ 問題のcollectionView(_:cellForItemAt:)メソッド

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : CustomCell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! CustomCell
cell.contentView.backgroundColor = UIColor.yellow
return cell
}

dequeueReusableCellメソッドで表示されなくなった Cell を使い回す実装になっているのだが、どうやら、Cell が前世の状態のまま使い回されるようだ。
そのため、前世の記憶(addSubview メソッドで加えられた チェックマークの view)がそのまま残っているというわけ。

なので、前世の記憶を一旦消して、現世のindexPathに応じてチェックマークを付けたり付けなかったりすればよい。

修正概要

具体的には、collectionView(_:cellForItemAt:) 内に次の処理を加えれば良い。

  1. 前世の記憶を削除する
// 古い subview を削除
cell.contentView.subviews.forEach { subview in
subview.removeFromSuperview()
}
  1. indexPathに応じてチェックマークを付け直す
if CustomCell.shouldCellBeMarked(selectedItemAt: indexPath) {
cell.mark(selectedItemAt: indexPath)
} else {
cell.unmark(deselectedItemAt: indexPath)
}

該当のindexPathの Cell にチェックマークを付け直すかどうかを判断するには
indexPathに対するチェックマーク有無を記憶しておく必要があるので、CustomCell に static 変数として用意した。

static var checkmarkStates: [Bool]

上記に合わせてその他細かな変更もあるが、それについてはソースコードを参照していただきたい。

ソースコード

修正後のソースコードは以下のとおり。

CustomCell.swift

https://gist.github.com/hahnah/ce942b0f01ce75eea2f86986d6cd4759#file-CustomCell.swift

CollectionViewController.swift

https://gist.github.com/hahnah/ce942b0f01ce75eea2f86986d6cd4759#file-CollectionViewController.swift

ExtendedCollectionViewController.swift

掲載略

こちらで見れます