StackView 内の CollectionView が消失しないようにするための方法
解決したかった問題
- 例えば 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
というエラーをもとに探ったところ、以下の記事を見つけました。
UICollectionViewFlowLayoutBreakForInvalidSizes が吐かれるのは大抵、想定されるサイズと制約で計算される実際のサイズとに差異がある場合です。つまり Auto Layout の組み方に問題があります。
こちらの記事では AutoLayout の組み方に問題があると述べられており、個人的にも StackView の高さの制約と CollectionView に設定した HeightConstraint が競合していそうだと思っていたので、その辺りで調査を続けました。
色々調査したところ、以下のような Stack Overflow の回答を発見しました。
こちらの回答では以下のように述べられていました。
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:
estimatedItemSize
を automaticSize
に設定して、計算されたセルのサイズが 50x50 以下の場合に発生しているのを見たことがある。つまり、estimatedItemSize
を automaticSize
に設定してセルの Self-Sizing のサポートを宣言している場合にこの問題は発生するということが述べられていました。
この問題を回避するために、上記の回答を踏まえつつ以下のように自分は問題を解決しました。
- CollectionView に高さを設定(高さは適当で良い)
-
estimatedItemSize
をCGSize
で適当に宣言 -
viewDidLayoutSubviews
やlayoutSubviews
などで CollectionView の高さを更新
少しだけ具体的にそれぞれ説明します。
CollectionView に高さを設定(高さは適当)
まずは CollectionView の HeightConstraint を適当に設定しておきます。後で高さは CollectionView の ContentSize の高さに合わせるので、適当な数値で問題ないです。
estimatedItemSize
を CGSize
で適当に宣言
主に今回の問題は estimatedItemSize
を automaticSize
に設定していたことが原因のものでした。
Stack Overflow の回答ににもありましたが、estimatedItemSize
は Apple Developer Documantation で以下のように説明されています。
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 の回答では estimatedSize
に CGSize(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
viewDidLayoutSubviews
や layoutSubviews
などで CollectionView の高さを更新
ここまででエラーは発生しなくなるはずですが、実際の CollectionView 内の ContentSize の高さと CollectionView の高さは一致させておきたいです。
そのために viewDidLayoutSubviews
や layoutSubViews
などで以下を呼んで一致させるようにします
override func layoutSubviews() {
collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
layoutIfNeeded()
}
自分の場合は以上の手順で問題を解決することができました。
もし、他に何か良い方法があったり、自分の解決方法に誤りがあれば教えていただけたら嬉しいです🙏
Discussion
見返してみると
の通りで単純に Auto Layout の組み方に問題があったような気がしている