1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-13

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

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

image.png

再現方法

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つ。

  1. 制約更新後のcollectionView.boundsの値を使用したいので、viewDidLoad()ではなくviewDidLayoutSubviews()でレイアウト設定をしている
  2. 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内の猫の写真がランダムに表示されます。
ダミーデータ作るのに便利です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?