UICollectionViewが少し下にスクロールされた状態で表示されてしまうバグへの対処方法

UICollectionViewを画面に表示したときに、少し下にスクロールされた状態で表示されてしまうという問題に遭遇しました。
具体的には、画面が表示された時のcontentOffset値が(0, 0)ではなく、なぜか(0, 80)のようになってしまうのです。

左図のようになってほしいのに、右図のようになってしまう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/81715/197af2a6-d18c-a8df-71bb-75f04972587a.png)

## 再現方法
UICollectionViewを持つビューコントローラのviewDidLayoutSubviews()内で、以下のようなコードを書く。

```swift
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
}
```

ポイントは以下の２つ。

1. 制約更新後のcollectionView.boundsの値を使用したいので、viewDidLoad()ではなくviewDidLayoutSubviews()でレイアウト設定をしている
2. UICollectionViewFlowLayoutを生成してUICollectionViewにアタッチしている

後述の参考記事によれば、セルがロードされるたびにviewDidLayoutSubviews()が実行され、そのたびにUICollectionViewFlowLayoutを生成してアタッチしていることが問題らしい。
参考記事ではUICollectionViewFlowLayoutを一度だけ生成するというソリューションを提案しているが、UICollectionViewにはデフォルトでUICollectionViewFlowLayoutがアタッチされているため、これを利用するという方法もある。

```swift
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()が何回呼ばれたかの判定が必要ないことである。

## 参考
https://samwize.com/2016/04/04/the-bug-to-do-with-uicollectionview-content-offset

## Appendix
ビューコントローラの実装載せておきます。

```swift
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内の猫の写真がランダムに表示されます。
ダミーデータ作るのに便利です。