UICollectionViewを画面に表示したときに、少し下にスクロールされた状態で表示されてしまうという問題に遭遇しました。
具体的には、画面が表示された時のcontentOffset値が(0, 0)ではなく、なぜか(0, 80)のようになってしまうのです。
左図のようになってほしいのに、右図のようになってしまう。
再現方法
UICollectionViewを持つビューコントローラのviewDidLayoutSubviews()内で、以下のようなコードを書く。
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupFlowLayout()
}
private func setupFlowLayout() {
// UICollectionViewFlowLayoutを生成
let layout = UICollectionViewFlowLayout()
// layoutのプロパティを変更
let insetValue: CGFloat = 24
layout.sectionInset = UIEdgeInsets(top: insetValue, left: insetValue, bottom: insetValue, right: insetValue)
let itemWidth = (collectionView.bounds.width - (insetValue * 3)) / 2
layout.itemSize = CGSize(width: itemWidth, height: 150)
layout.minimumLineSpacing = 24
layout.minimumInteritemSpacing = 24
// 生成したUICollectionViewFlowLayoutをUICollectionViewにアタッチ
collectionView.collectionViewLayout = layout
}
ポイントは以下の2つ。
- 制約更新後のcollectionView.boundsの値を使用したいので、viewDidLoad()ではなくviewDidLayoutSubviews()でレイアウト設定をしている
- UICollectionViewFlowLayoutを生成してUICollectionViewにアタッチしている
後述の参考記事によれば、セルがロードされるたびにviewDidLayoutSubviews()が実行され、そのたびにUICollectionViewFlowLayoutを生成してアタッチしていることが問題らしい。
参考記事ではUICollectionViewFlowLayoutを一度だけ生成するというソリューションを提案しているが、UICollectionViewにはデフォルトでUICollectionViewFlowLayoutがアタッチされているため、これを利用するという方法もある。
private func setupFlowLayout() {
// let layout = UICollectionViewFlowLayout()
// デフォルトでアタッチされているUICollectionViewFlowLayoutを利用する
guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
// layoutのプロパティを変更
let insetValue: CGFloat = 24
layout.sectionInset = UIEdgeInsets(top: insetValue, left: insetValue, bottom: insetValue, right: insetValue)
let itemWidth = (collectionView.bounds.width - (insetValue * 3)) / 2
layout.itemSize = CGSize(width: itemWidth, height: 150)
layout.minimumLineSpacing = 24
layout.minimumInteritemSpacing = 24
// collectionView.collectionViewLayout = layout
}
こちらの方法のメリットは、setupFlowLayout()が何回呼ばれたかの判定が必要ないことである。
参考
Appendix
ビューコントローラの実装載せておきます。
import UIKit
import Kingfisher
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
var count = 0
let urls = [
"https://loremflickr.com/cache/resized/4624_39775690534_ec353a6a84_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/4605_25858416078_ff64586d2c_n_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/1764_28268715117_efaa5b9355_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/4716_25902768928_44dcb01e84_n_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/4524_24561122738_3acff21e9d_n_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/1784_42783188432_c8da75b6e4_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/4404_36747142743_d62fc4a4f9_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/4679_24320463337_0bf71317da_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/792_40797338952_83b99e38c9_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/803_41112504462_0c9238c6f5_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/4528_38912908481_7e3b3cee8c_320_240_nofilter.jpg",
"https://loremflickr.com/cache/resized/4556_38186124272_f3a77f4c1c_n_320_240_nofilter.jpg"
]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.lightGray
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(collectionView.contentOffset) // contentOffset値のデバッグ
setupFlowLayout()
print(collectionView.contentOffset) // contentOffset値のデバッグ
}
private func setupFlowLayout() {
// let layout = UICollectionViewFlowLayout()
guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
let insetValue: CGFloat = 24
layout.sectionInset = UIEdgeInsets(top: insetValue, left: insetValue, bottom: insetValue, right: insetValue)
let itemWidth = (collectionView.bounds.width - (insetValue * 3)) / 2
layout.itemSize = CGSize(width: itemWidth, height: 150)
layout.minimumLineSpacing = 24
layout.minimumInteritemSpacing = 24
// collectionView.collectionViewLayout = layout
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return urls.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? CollectionViewCell else { return UICollectionViewCell() }
let url = URL(string: urls[indexPath.row])
cell.imageView.kf.setImage(with: url)
return cell
}
}
バグを再現したときのcontentOffset値のデバッグログ。
(0.0, 0.0)
(0.0, 0.0)
(0.0, 0.0)
(0.0, 80.0)
おまけ
写真は https://loremflickr.com/ から拝借しました。
ブラウザで https://loremflickr.com/320/240 にアクセスすると、Flickr内の猫の写真がランダムに表示されます。
ダミーデータ作るのに便利です。