この記事を書くに至った経緯
TableViewCell の中に、CollectionViewを配置する手段を用いて開発をしていました。 その中で、CollectionViewは2列で表示されているのに、TableViewCellの長さはCollectionView1列分の長さになっているという事象が発生しました。 この問題の解決に2日要したので記事にしようと思いました。
再現gif

作業環境
- macOS Catalina
- version 10.15.5
- Xcode
- version 12.3
- iPhone12 Pro Max (シミュレーター)
- iOS 14.3
- Swift
- version 5.3.2
TOC(Table Of Contents)
- 発生した問題
- 解決策
- 高さが揃わない事象
- TableViewCellの画面更新を行わない場合
- 解決策コードを記述しなかった場合
- layoutIfNeeded()の動き
- まとめ
- 最後に
- 参考サイト一覧
発生した問題
TableViewCellの高さが決まるタイミングと、CollectionViewのセルの高さが決まるタイミングにズレがあるのか、期待通りの高さになりませんでした。
なんとなく、TableViewCellをlayoutIfNeeded()で画面更新すれば解決するのではないかと思っていましたが、そのタイミングが分からず苦戦しました。
原因については、現時点では推測です。解決策とともに、原因についても本記事でご紹介します。
解決策
TableViewCellのクラス内で、以下の記述を行うことで解決することができました。
self.layoutIfNeeded() を行うことで、TableViewCell自身の画面更新と、TableView配下のView(CollectionViewとその中)のレイアウト更新を行い、情報を新しいものに更新することができます。
以下の
systemLayoutSizeFitting() というメソッドは、TableViewCellが作成された段階で読み込まれます。つまりこの時点で、親Viewに対して layoutIfNeeded() を行っておけば、レイアウト更新のフラグが立った時点で更新が行われる為、レイアウト崩れを起こさなくなります。
コード
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
//TableViewCellのlayoutIfNeeded()
self.layoutIfNeeded()
let contentSize = self.collectionView.collectionViewLayout.collectionViewContentSize
return CGSize(width: contentSize.width, height: contentSize.height)
}
gif

各メソッド内でprint()をして動きを見る
レイアウト更新が走るタイミングと、その他説明をコメント文で記載しました。
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: cellForRowAt
TableView: heightForRowAt UITableView.automaticDimension -1.0
TableView: cellForRowAt ViewFrame (0.0, 0.0, 428.0, 926.0)
TableView: cellForRowAt HeaderViewFrame (0.0, 0.0, 428.0, 299.66666666666663)
TableView: cellForRowAt TableViewCellFrame (0.0, 0.0, 359.0, 591.0)
TableView: Put viewWidth Into TableViewCell Property
TableView: heightForRowAt UITableView.automaticDimension -1.0
TableViewCell: systemLayoutSizeFitting
TableViewCell: systemLayoutSizeFitting CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
TableViewCell: systemLayoutSizeFitting TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
//TableViewCellのレイアウト更新が走ります。
layoutIfNeeded
//CollectionViewのレイアウト更新が走ります。
//このlayoutSubviews()で、CollectionView と TableViewCell の size(width・height) が揃います。
//これ以降、CollectionViewCell 二列分の幅が担保されます。
//故に、CollectionViewCell 二列分で計算が行われ、collectionViewContentSize の height が決定されます。
layoutSubviews
CollectionView: numberOfSections
CollectionView: numberOfSections CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: numberOfSections TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: numberOfItemsInSection
CollectionView: numberOfItemsInSection CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: numberOfItemsInSection TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: sizeForItemAt
CollectionView: sizeForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: sizeForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
・
・ 中略
・
CollectionView: sizeForItemAt
CollectionView: sizeForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: sizeForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: insetForSectionAt
CollectionView: insetForSectionAt CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: insetForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: minimumInteritemSpacingForSectionAt
CollectionView: minimumInteritemSpacingForSectionAt CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: minimumInteritemSpacingForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: minimumLineSpacingForSectionAt
CollectionView: minimumLineSpacingForSectionAt CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: minimumLineSpacingForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
//CollectionViewCell のレイアウト更新が走ります。
layoutSubviews
TableViewCell: systemLayoutSizeFitting CollectionViewFrame (0.0, 0.0, 428.0, 44.0)
TableViewCell: systemLayoutSizeFitting TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
TableViewCell: systemLayoutSizeFitting collectionViewContentSize: (428.0, 2044.0)
//TableViewCell のレイアウト更新が走ります。
//ここで、CollectionView と TableViewCell の size(width・height) が更新されます。
layoutIfNeeded
//CollectionView のレイアウト更新が走ります。
layoutSubviews
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 2044.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.6666666666665, 428.0, 2044.0)
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
・
・ 中略
・
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 2044.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.6666666666665, 428.0, 2044.0)
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
高さが揃わない事象
TableViewCellの画面更新を行わない場合
コード
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
//TableViewCellではなく、CollectionViewに対して、layoutIfNeeded()を実行
collectionView.layoutIfNeeded()
let contentSize = self.collectionView.collectionViewLayout.collectionViewContentSize
return CGSize(width: contentSize.width, height: contentSize.height)
}
gif

各メソッド内でprint()をして動きを見る
レイアウト更新が走るタイミングと、その他説明をコメント文で記載しました。
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: cellForRowAt
TableView: heightForRowAt UITableView.automaticDimension -1.0
TableView: cellForRowAt ViewFrame (0.0, 0.0, 428.0, 926.0)
TableView: cellForRowAt HeaderViewFrame (0.0, 0.0, 428.0, 299.66666666666663)
TableView: cellForRowAt TableViewCellFrame (0.0, 0.0, 359.0, 591.0)
TableView: Put viewWidth Into TableViewCell Property
TableView: heightForRowAt UITableView.automaticDimension -1.0
TableViewCell: systemLayoutSizeFitting
TableViewCell: systemLayoutSizeFitting CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
TableViewCell: systemLayoutSizeFitting TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
//この時点で、CollectionViewFrame の width が TableViewCellFrame の width より小さいので、CollectionViewCell 二列分の幅が担保されません。
//故に、一列分の高さが計算され、collectionViewContentSize の height が決定されます。
CollectionView: numberOfSections
CollectionView: numberOfSections CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: numberOfSections TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: numberOfItemsInSection
CollectionView: numberOfItemsInSection CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: numberOfItemsInSection TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: sizeForItemAt
CollectionView: sizeForItemAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: sizeForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
・
・ 中略
・
CollectionView: sizeForItemAt
CollectionView: sizeForItemAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: sizeForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: insetForSectionAt
CollectionView: insetForSectionAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: insetForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: minimumInteritemSpacingForSectionAt
CollectionView: minimumInteritemSpacingForSectionAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: minimumInteritemSpacingForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: minimumLineSpacingForSectionAt
CollectionView: minimumLineSpacingForSectionAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: minimumLineSpacingForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: cellForItemAt collectionViewContentSize: (359.0, 4064.0)
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: cellForItemAt collectionViewContentSize: (359.0, 4064.0)
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
CollectionView: cellForItemAt collectionViewContentSize: (359.0, 4064.0)
TableViewCell: systemLayoutSizeFitting CollectionViewFrame (0.0, 0.0, 359.0, 591.0)
TableViewCell: systemLayoutSizeFitting TableViewCellFrame (0.0, 299.66666666666663, 428.0, 44.0)
TableViewCell: systemLayoutSizeFitting collectionViewContentSize: (359.0, 4064.0)
//TableViewCell のレイアウト更新が走ります。
layoutIfNeeded
//CollectionView のレイアウト更新が走ります。
//ここで、CollectionView と TableViewCell の size(width・height) が揃います。
layoutSubviews
//CollectionViewCell のレイアウト更新が走ります。
//二列文の幅が担保されたので、CollectionViewCell に更新フラグが立ち、更新が行われます。
layoutSubviews
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 4064.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.6666666666665, 428.0, 4064.0)
//更新が行われたことにより、collectionViewContentSize の height が 二列分の高さに変わっています。
//これ以降、layoutIfNeeded()が行われないため、CollectionViewとTableViewCellの高さは、一列分の高さのままというわけです。
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
・
・ 中略(Cellの個数分実行)
・
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 4064.0)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.6666666666665, 428.0, 4064.0)
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
解決策コードを記述しなかった場合
スクリーンショット

各メソッド内でprint()をして動きを見る
レイアウト更新が走るタイミングと、その他説明をコメント文で記載しました。
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: numberOfSections
TableView: numberOfRowsInSection
TableView: cellForRowAt
TableView: heightForRowAt UITableView.automaticDimension -1.0
TableView: cellForRowAt ViewFrame (0.0, 0.0, 428.0, 926.0)
TableView: cellForRowAt HeaderViewFrame (0.0, 0.0, 428.0, 299.66666666666663)
TableView: cellForRowAt TableViewCellFrame (0.0, 0.0, 359.0, 591.0)
TableView: Put viewWidth Into TableViewCell Property
TableView: heightForRowAt UITableView.automaticDimension -1.0
//TableViewCell の ContentView の高さが曖昧なので、TableViewCell の frame を適応するという警告文
//TableViewCell にレイアウト更新フラグが立ちます。
2020-12-22 14:23:53.811492+0900 CollectionViewInTableViewCell[7926:7591780] [Warning] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a table view cell's content view. We're considering the collapse unintentional and using standard height instead. Cell: <CollectionViewInTableViewCell.TableViewCell: 0x7fa95602ba00; baseClass = UITableViewCell; frame = (0 299.667; 428 44); autoresize = W; layer = <CALayer: 0x6000029a4360>>
//TableViewCell の更新が走ります。
layoutIfNeeded
//CollectionView の更新が走ります。
layoutSubviews
CollectionView: numberOfSections
CollectionView: numberOfItemsInSection
//Cellのサイズを計算
CollectionView: sizeForItemAt
CollectionView: sizeForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 43.666666666666664)
CollectionView: sizeForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 43.666666666666664)
・
・ 中略
・
CollectionView: sizeForItemAt
CollectionView: sizeForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 43.666666666666664)
CollectionView: sizeForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 43.666666666666664)
CollectionView: insetForSectionAt
CollectionView: insetForSectionAt CollectionViewFrame (0.0, 0.0, 428.0, 43.666666666666664)
CollectionView: insetForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 43.666666666666664)
CollectionView: minimumInteritemSpacingForSectionAt
CollectionView: minimumInteritemSpacingForSectionAt CollectionViewFrame (0.0, 0.0, 428.0, 43.666666666666664)
CollectionView: minimumInteritemSpacingForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 43.666666666666664)
CollectionView: minimumLineSpacingForSectionAt
CollectionView: minimumLineSpacingForSectionAt CollectionViewFrame (0.0, 0.0, 428.0, 43.666666666666664)
CollectionView: minimumLineSpacingForSectionAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 43.666666666666664)
//self.collectionView.collectionViewLayout.collectionViewContentSize が決定
//Viewの更新が入らないので、上記の minimumLineSpacingForSectionAt 時点でのframeで描画される事になります。
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 43.666666666666664)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 43.666666666666664)
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
CollectionView: cellForItemAt
CollectionView: cellForItemAt CollectionViewFrame (0.0, 0.0, 428.0, 43.666666666666664)
CollectionView: cellForItemAt TableViewCellFrame (0.0, 299.66666666666663, 428.0, 43.666666666666664)
CollectionView: cellForItemAt collectionViewContentSize: (428.0, 2044.0)
layoutSubviews
layoutIfNeeded()の動き
systemLayoutSizeFitting() をoverride して layoutIfNeeded() をしている事象を見てみると、自分自身(ここではtableViewCell)と配下のViewの更新が全て終わるまで、次の動きを待機していることがわかります。
調べてみると、あるサイトに、
この処理を呼んだ場合再描画処理が同期実行されます。その為、再描画が完了するまで呼び出し側の実行が止まります。
と記載してありました。
また、layoutIfNeeded()を行うと、親View → 子View の順番でレイアウトの更新が走ります。
参考サイト
- Qiita – 「setNeedsDisplay」、「setNeedsLayout」、「layoutIfNeeded」、「layoutSubviews」の違い
- Qiita – UIKitのView表示ライフサイクルを理解する
- UIView が持つ描画・レイアウト更新系のメソッドメモ
まとめ
根本原因
いろいろな場所で
print() をした結果、今回の原因は、タイミングではありませんでした。
- CollectionView の width を TableViewCell の width に合わせたか
- CollectionViewCell二列分の幅が、サイズ計算が始まる前に担保されていたか
上記の2点だったように思います。
今回のコードでは、以下のように、CollectionViewCell の width を TableViewCell の width の約半分として設定しています。
let widthHeight = (viewWidth - insetWidth) / 2
return CGSize(width: widthHeight, height: widthHeight)
故に、CollectionView の width が、TableViewCell の width より狭かった場合、CollectionViewCell が二列収まらなくなり、一列分として計算を始めてしまいます。
解決策(再掲)
TableViewCell の中に、CollectionView を入れるときは、
systemLayoutSizeFitting()をoverride- その中で TableViewCell の
layoutIfNeeded()を実行
CollectionView の size を TableViewCell の size に更新してくれます。
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
//TableViewCellのlayoutIfNeeded()
self.layoutIfNeeded()
let contentSize = self.collectionView.collectionViewLayout.collectionViewContentSize
return CGSize(width: contentSize.width, height: contentSize.height)
}
最後に
これから、UITableViewが使われなくなっていくと聞いていますが、もし CollectionView in TableViewCell という複雑なやり方をするのであれば、参考にしていただければと思います。
最後まで記事を読んで頂き、ありがとうございました。
参考サイト一覧
- 解決策(
systemLayoutSizeFitting()をoverrideしてその中で、TableViewCell のlayoutIfNeeded()を実行する) - layoutIfNeeded()の動き
澁谷太智