IndexPathに気をつけろ!
はじめに
UICollectionViewでレイアウト計算を行う際にIndexPathを自作して問題が起きました。
今回実現したかったことは、UICollectionViewをスクロールして対象のcellが画面内に100%表示されているかを判定することです。
CollectionViewのcellが画面に表示するかの判定処理は以下です。
現在画面に表示されている部分のCollectionViewの座標はcollectionView.boundsで取れるのでそこで取っています。今回の要件的にY座標でスクロールはしないものだったのでX座標だけで判定しています。
private func isPerfectVisibleCell(cellRect: CGRect) -> Bool {
let collectionViewRect = collectionView.bounds
let collectionViewMaxX = collectionViewRect.maxX
let collectionViewMinX = collectionViewRect.minX
let cellMaxX = cellRect.maxX
let cellMinX = cellRect.minX
return cellMinX > collectionViewMinX && cellMaxX < collectionViewMaxX
}
問題の箇所
実際に処理行う箇所ですか以下のようなコードでcollectionViewのlayoutAttributesForItemに自作のIndexPathを渡してセルのCGRectを取得しようとしていました。
一見何も問題ないように見えますが、cellRectで取得できるCGRectの値が正しいものではありませんでした。
for i in 0..<viewModel.cellViewModel.count {
let indexPath = IndexPath(row: i, section: 0)
guard let cellRect: CGRect = collectionView.layoutAttributesForItem(at: indexPath)?.frame
else {
return
}
if isPerfectVisibleCell(cellRect: cellRect) {
// やりたい処理処理
}
}
ここからはあくまで推測でしかないのですが、collectionView.layoutAttributesForItemで引数に渡すIndexPathは中身のrowのcountを使ってcellを見つけるのではなく、collectionViewの内部に保持しているcellのアドレスをIndexPathを使って参照して持ってきているんじゃないかと思っています。
解決方法
cellを作成する際に使用するIndexPathを変数として保持しておいてそちらを使ってcollectionView.layoutAttributesForItemを呼び出すことで正しいcellの座標とサイズを取得することができました。
indexPathList.forEach { indexPath in
guard let cellRect: CGRect = collectionView.layoutAttributesForItem(at: indexPath)?.frame
else {
return
}
if isPerfectVisibleCell(cellRect: cellRect) {
// やりたい処理処理
}
}
※cellの生成は画面回転やreload時にも呼ばれてしまうので、変数として保持しているIndexPathにすでに保存してあるIndexPathはappendしない処理を書いておかないと重複して保持してしまうので注意!!