UICollectionView を使用して、Cell の幅などを動的にしたい時って結構ありますよね。そんな時は、下記のように estimatedItemSize
に UICollectionViewFlowLayout.automaticSize
を使用する場合が多いかと思います。
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
これは iOS10 以降から使用できるグローバル変数なのですが、この記事でも記載されている通り、iOS10 ではすごくバグが多くてあまり使えなかったみたいですが、この現象は iOS11 以降でもちらほら見られることがあったので、そのような場合の具体的な改善策を今回は残しておこうかと思います。
※ 今回のまとめは横スクロールの CollectionView に関するものです
具体的に発生した問題
- 初回表示時に Cell がなぜかスクエアで表示されて、スクロールするとコンテンツの幅に合わせて正しい Cell のサイズになおる(iOS12.1)
- https://stackoverflow.com/questions/51375566/in-ios-12-when-does-the-uicollectionview-layout-cells-use-autolayout-in-nib と同じ状況
- iOS12.4 ではこの問題は発生していないので、12.1~<12.4 の問題だと考えられます。
- 表示されない Cell がある(iOS12系)
- 1つ目の Section と、2つ目の Section の Cell が重なって表示される(iOS13系)
- 更新アニメーションが変な動きをする(iOS11系)
- Cell のサイズを正しく計算できていないのが原因と考えられる
改善策
具体的な改善策は下記の2つです。
- estimatedItemSize に
UICollectionViewFlowLayout.automaticSize
をセットするのではなく、FlowLayot の Delegate を使用して ItemSize を決定する。 systemLayoutSizeFitting
を活用して AutoLayout後の Cell の正しいサイズを取得する。
では、実際にコードを書いていきましょう。Cell のサイズを正しく取得するために、ダミーの Cell インスタンスを取得するためのプロパティを作成しておきます。
private lazy var cellHelper: HogeCollectionViewCell? = { let object = UINib(nibName: "HogeCollectionViewCell", bundle: nil).instantiate(withOwner: nil).first return object as? HogeCollectionViewCell }()
作成した ダミー Cell に実際に使用する Text などをセットして、サイズを取得できるようにします。systemLayoutSizeFitting
で制約に基づいた Cell の正しいサイズを取得して、Delegate で返せば正しく Cell が表示されるようになります🎉
UIView.layoutFittingCompressedSize
を設定することで、制約の範囲で最も小さいサイズが返されるようになります。制約の範囲で最も大きいサイズを返したい場合は layoutFittingExpandedSize
を使用します。
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let dataSource = viewModel.dataSources[indexPath.section] let data = dataSource.elements[indexPath.row] if let cell = cellHelper { cell.set(iconImageUrl: data.imageUrl, amount: data.amount, teamColor: data.teamColor) return cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) } return .zero }
以前にも、CollectionView のバグに関する記事を書きましたが、FlowLayout 周りのところは iOS13 でもちらほらバグがあるので、基本的には FlowLayoutDelegate を使用した方が全体的にいい気がしました。また、今回のバグについてはどのような状況で発生しているのかが要素が多すぎて判断しきれなかったので、わかり次第記載しようかと思います。(個人的には、横スクロール時にこのようなバグが発生するケースが多かったので、width を動的に変更する場合などが関係しているのではという気もしています🤔)
参考
- https://developer.apple.com/forums/thread/105523
- https://stackoverflow.com/questions/51375566/in-ios-12-when-does-the-uicollectionview-layout-cells-use-autolayout-in-nib/52428617#52428617
- https://stackoverflow.com/questions/51375566/in-ios-12-when-does-the-uicollectionview-layout-cells-use-autolayout-in-nib/52428617#52428617
- https://developer.apple.com/documentation/uikit/uicollectionviewflowlayoutautomaticsize?language=objc
- https://qiita.com/usagimaru/items/e0a4c449d4cdf341e152