🤖

StackView 内の CollectionView が消失しないようにするための方法

2021/03/09に公開1

解決したかった問題

  • 例えば StackView に CollectionView を入れている時などに起きる問題
  • 以下のような CollectionView を StackView に入れた時(vertical 方向)に自分の場合は発生しました(自分の場合は iOS11 以下のみ。iOS12 以降は正常に動作するという挙動でした)
@IBOutlet private weak var collectionView: UICollectionView!

//...

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = UICollectionView.automaticSize
layout.invalidateLayout()
collectionView.collectionViewLayout = layout
  • 問題は StackView に入れたはずの CollectionView の高さが 0 になってしまい、CollectionView が全く見えない状態となってしまっていたこと
  • HeightConstraint を張れば解決するかもと思い、適当に張ってみましたがそれだけでは解決せずという状況でした
  • HeightConstraint を張っただけだと UICollectionViewFlowLayoutBreakForInvalidSizes というエラーが発生しました

解決方法

UICollectionViewFlowLayoutBreakForInvalidSizes というエラーをもとに探ったところ、以下の記事を見つけました。
https://qiita.com/usagimaru/items/e0a4c449d4cdf341e152

UICollectionViewFlowLayoutBreakForInvalidSizes が吐かれるのは大抵、想定されるサイズと制約で計算される実際のサイズとに差異がある場合です。つまり Auto Layout の組み方に問題があります。

こちらの記事では AutoLayout の組み方に問題があると述べられており、個人的にも StackView の高さの制約と CollectionView に設定した HeightConstraint が競合していそうだと思っていたので、その辺りで調査を続けました。

色々調査したところ、以下のような Stack Overflow の回答を発見しました。
https://stackoverflow.com/a/52993424/13738685

こちらの回答では以下のように述べられていました。

I've also seen this occur when we set the estimatedItemSize to automaticSize and the computed size of the cells is less than 50x50 (Note: This answer was valid at the time of iOS 12. Maybe later versions have it fixed).
i.e. if you are declaring support for self-sizing cells by setting the estimated item size to the automaticSize constant:

estimatedItemSizeautomaticSize に設定して、計算されたセルのサイズが 50x50 以下の場合に発生しているのを見たことがある。つまり、estimatedItemSizeautomaticSize に設定してセルの Self-Sizing のサポートを宣言している場合にこの問題は発生するということが述べられていました。

この問題を回避するために、上記の回答を踏まえつつ以下のように自分は問題を解決しました。

  • CollectionView に高さを設定(高さは適当で良い)
  • estimatedItemSizeCGSize で適当に宣言
  • viewDidLayoutSubviewslayoutSubviews などで CollectionView の高さを更新

少しだけ具体的にそれぞれ説明します。

CollectionView に高さを設定(高さは適当)

まずは CollectionView の HeightConstraint を適当に設定しておきます。後で高さは CollectionView の ContentSize の高さに合わせるので、適当な数値で問題ないです。

estimatedItemSizeCGSize で適当に宣言

主に今回の問題は estimatedItemSizeautomaticSize に設定していたことが原因のものでした。
Stack Overflow の回答ににもありましたが、estimatedItemSize は Apple Developer Documantation で以下のように説明されています。

https://developer.apple.com/documentation/uikit/uicollectionviewflowlayout/1617709-estimateditemsize

The default value of this property is CGSizeZero. Setting it to any other value, like automaticSize, causes the collection view to query each cell for its actual size using the cell’s preferredLayoutAttributesFitting(_:) method.

estimatedSize はデフォルトでは CGSizeZero で、automaticSize のように他の値を estimatedSize に設定すると CollectionView は各セルのサイズを実際に照会すると述べられています。
そのため、Stack Overflow の回答では estimatedSizeCGSize(width: 1, height: 1) のような適当な値を設定しておくことで問題を回避できると述べられていました。
今回の自分の場合で言うと、以下のように設定しました。

@IBOutlet private weak var collectionView: UICollectionView!

//...

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = CGSize(width: 1, height: 1)
layout.invalidateLayout()
addressCollectionView.collectionViewLayout = layout

viewDidLayoutSubviewslayoutSubviews などで CollectionView の高さを更新

ここまででエラーは発生しなくなるはずですが、実際の CollectionView 内の ContentSize の高さと CollectionView の高さは一致させておきたいです。
そのために viewDidLayoutSubviewslayoutSubViews などで以下を呼んで一致させるようにします

override func layoutSubviews() {
    collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
    layoutIfNeeded()
}

自分の場合は以上の手順で問題を解決することができました。
もし、他に何か良い方法があったり、自分の解決方法に誤りがあれば教えていただけたら嬉しいです🙏

Discussion

アイカワアイカワ

見返してみると

つまり Auto Layout の組み方に問題があります。

の通りで単純に Auto Layout の組み方に問題があったような気がしている