アプリケーション画面の1つでは、UICollectionView
の内側にUITableViewCell
を配置する必要があります。このUICollectionView
には動的な数のアイテムが含まれるため、動的に計算する必要がある高さにもなります。ただし、埋め込みUICollectionView
の高さを計算しようとすると問題が発生します。
包括的なUIViewController
はStoryboardsで作成され、自動レイアウトを使用しています。しかし、UITableViewCell
の高さに基づいてUICollectionView
の高さを動的に増やす方法はわかりません。
誰もこれを達成する方法に関するヒントやアドバイスを提供できますか?
正解はYES、あなたはCANこれを行います。
数週間前にこの問題に出会いました。実際には思っているより簡単です。セルをNIB(またはストーリーボード)に配置し、それらを固定して、自動レイアウトにすべての作業を行わせる
次の構造を考えます:
TableView
TableViewCell
CollectionView
CollectionViewCell
CollectionViewCell
CollectionViewCell
[...可変数のセルまたは異なるセルサイズ]
解決策は、最初にcollectionViewCellサイズを計算し、次にコレクションビューのcontentSizeを計算し、それをセルのサイズとして使用するように自動レイアウトに指示することです。これはIView「マジックを行う」メソッドです:
-(void)systemLayoutSizeFittingSize:(CGSize)targetSize
withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority
verticalFittingPriority:(UILayoutPriority)verticalFittingPriority
ここでは、TableViewCellのサイズを設定する必要があります。この場合、これはCollectionViewのcontentSizeです。
CollectionViewCellでは、モデルを変更するたびにレイアウトするようにセルに指示する必要があります(たとえば、テキストでUILabelを設定した後、セルを再度レイアウトする必要があります)。
- (void)bindWithModel:(id)model {
// Do whatever you may need to bind with your data and
// tell the collection view cell's contentView to resize
[self.contentView setNeedsLayout];
}
// Other stuff here...
TableViewCellは魔法をかけます。 collectionViewへのアウトレットがあり、ICollectionViewFlowLayoutのestimatedItemSizeを使用してcollectionViewセルの自動レイアウトを有効にします。
次に、トリックはsystemLayoutSizeFittingSize...メソッドでtableViewセルのサイズを設定することです。 (注:iOS8以降)
注:tableView -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
。のデリゲートセルの高さメソッドを使用しようとしましたが、自動レイアウトシステムがCollectionView contentSizeを計算するために手遅れですサイズ変更されたセルが間違っている場合があります。
@implementation TableCell
- (void)awakeFromNib {
[super awakeFromNib];
UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
// Configure the collectionView
flow.minimumInteritemSpacing = ...;
// This enables the magic of auto layout.
// Setting estimatedItemSize different to CGSizeZero
// on flow Layout enables auto layout for collectionView cells.
// https://developer.Apple.com/videos/play/wwdc2014-226/
flow.estimatedItemSize = CGSizeMake(1, 1);
// Disable the scroll on your collection view
// to avoid running into multiple scroll issues.
[self.collectionView setScrollEnabled:NO];
}
- (void)bindWithModel:(id)model {
// Do your stuff here to configure the tableViewCell
// Tell the cell to redraw its contentView
[self.contentView layoutIfNeeded];
}
// THIS IS THE MOST IMPORTANT METHOD
//
// This method tells the auto layout
// You cannot calculate the collectionView content size in any other place,
// because you run into race condition issues.
// NOTE: Works for iOS 8 or later
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority {
// With autolayout enabled on collection view's cells we need to force a collection view relayout with the shown size (width)
self.collectionView.frame = CGRectMake(0, 0, targetSize.width, MAXFLOAT);
[self.collectionView layoutIfNeeded];
// If the cell's size has to be exactly the content
// Size of the collection View, just return the
// collectionViewLayout's collectionViewContentSize.
return [self.collectionView.collectionViewLayout collectionViewContentSize];
}
// Other stuff here...
@end
TableViewControllerでtableViewセルの自動レイアウトシステムを有効にしてください。
- (void)viewDidLoad {
[super viewDidLoad];
// Enable automatic row auto layout calculations
self.tableView.rowHeight = UITableViewAutomaticDimension;
// Set the estimatedRowHeight to a non-0 value to enable auto layout.
self.tableView.estimatedRowHeight = 10;
}
クレジット: @ rbarbera これを整理するのに役立ちました
私の方法ははるかにシンプルだと思い、@ PabloRomeuによって提案されました。
手順1. UICollectionView
からUITableViewCell subclass
へのアウトレットを作成します。ここには、UICollectionView
が配置されます。名前はcollectionView
になります
ステップ2. UICollectionView
の高さ制約にIBを追加し、UITableViewCell subclass
へのアウトレットも作成します。名前はcollectionViewHeight
になります。
ステップ3. tableView:cellForRowAtIndexPath:
でコードを追加します。
// deque a cell
cell.frame = tableView.bounds;
[cell layoutIfNeeded];
[cell.collectionView reloadData];
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height;
テーブルビューとコレクションビューはいずれもUIScrollView
サブクラスであるため、コンテンツサイズの計算、セルの再利用などを試みるため、別のスクロールビュー内に埋め込むのは好ましくありません。
あらゆる目的でコレクションビューのみを使用することをお勧めします。
それをセクションに分割し、一部のセクションのレイアウトをテーブルビューとして、その他をコレクションビューとして「扱う」ことができます。結局のところ、テーブルビューでできるコレクションビューでは達成できないことは何もありません。
コレクションビュー「パーツ」の基本的なグリッドレイアウトがある場合は、通常のテーブルセルを使用してそれらを処理することもできます。それでもiOS 5のサポートが必要ない場合は、コレクションビューを使用することをお勧めします。
上記のPablo Romeuの回答( https://stackoverflow.com/a/33364092/2704206 )は、私の問題に非常に役立ちました。しかし、これを私の問題のために機能させるには、いくつかのことを異なる方法で行わなければなりませんでした。まず、layoutIfNeeded()
を頻繁に呼び出す必要はありませんでした。 collectionView
関数のsystemLayoutSizeFitting
で呼び出す必要がありました。
次に、テーブルビューセルのコレクションビューにパディングを与えるための自動レイアウト制約がありました。そのため、targetSize.width
の幅を設定するときに、collectionView.frame
から先頭と末尾のマージンを差し引く必要がありました。また、戻り値CGSize
の高さに上下のマージンを追加する必要がありました。
これらの制約定数を取得するために、制約へのアウトレットを作成するか、定数をハードコーディングするか、識別子でルックアップするオプションがありました。カスタムテーブルビューセルクラスを簡単に再利用できるようにするために、3番目のオプションを選択することにしました。結局、これは私がそれを機能させるために必要なすべてでした:
class CollectionTableViewCell: UITableViewCell {
// MARK: -
// MARK: Properties
@IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionViewLayout?.estimatedItemSize = CGSize(width: 1, height: 1)
selectionStyle = .none
}
}
var collectionViewLayout: UICollectionViewFlowLayout? {
return collectionView.collectionViewLayout as? UICollectionViewFlowLayout
}
// MARK: -
// MARK: UIView functions
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
collectionView.layoutIfNeeded()
let topConstraintConstant = contentView.constraint(byIdentifier: "topAnchor")?.constant ?? 0
let bottomConstraintConstant = contentView.constraint(byIdentifier: "bottomAnchor")?.constant ?? 0
let trailingConstraintConstant = contentView.constraint(byIdentifier: "trailingAnchor")?.constant ?? 0
let leadingConstraintConstant = contentView.constraint(byIdentifier: "leadingAnchor")?.constant ?? 0
collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width - trailingConstraintConstant - leadingConstraintConstant, height: 1)
let size = collectionView.collectionViewLayout.collectionViewContentSize
let newSize = CGSize(width: size.width, height: size.height + topConstraintConstant + bottomConstraintConstant)
return newSize
}
}
識別子によって制約を取得するヘルパー関数として、次の拡張子を追加します。
extension UIView {
func constraint(byIdentifier identifier: String) -> NSLayoutConstraint? {
return constraints.first(where: { $0.identifier == identifier })
}
}
注:ストーリーボードまたはこれらが作成されている場所で、これらの制約に識別子を設定する必要があります。定数が0でない限り、問題ではありません。また、Pabloの応答のように、UICollectionViewFlowLayout
をコレクションビューのレイアウトとして使用する必要があります。最後に、collectionView
IBOutletをストーリーボードにリンクしてください。
上記のカスタムテーブルビューセルを使用して、コレクションビューを必要とする他のテーブルビューセルにサブクラス化し、UICollectionViewDelegateFlowLayout
およびUICollectionViewDataSource
プロトコルを実装することができます。これが他の誰かに役立つことを願っています!
おそらく私のバリアントが役に立つでしょう。過去2時間このタスクを決定しました。私はそれが100%正しいか、または最適であるふりをしません、しかし、私のスキルはまだ非常に小さいです、そして、私は専門家からコメントを聞きたいです。ありがとうございました。 1つの重要な注意:これは静的テーブルで機能します-現在の作業で指定されています。そのため、私が使用するのはviewWillLayoutSubviews of tableViewのみです。そしてもう少し。
private var iconsCellHeight: CGFloat = 500
func updateTable(table: UITableView, withDuration duration: NSTimeInterval) {
UIView.animateWithDuration(duration, animations: { () -> Void in
table.beginUpdates()
table.endUpdates()
})
}
override func viewWillLayoutSubviews() {
if let iconsCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 1)) as? CategoryCardIconsCell {
let collectionViewContentHeight = iconsCell.iconsCollectionView.contentSize.height
if collectionViewContentHeight + 17 != iconsCellHeight {
iconsCellHeight = collectionViewContentHeight + 17
updateTable(tableView, withDuration: 0.2)
}
}
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch (indexPath.section, indexPath.row) {
case ...
case (1,0):
return iconsCellHeight
default:
return tableView.rowHeight
}
}
コレクションビュークラスに静的メソッドを配置し、それが持つコンテンツに基づいてサイズを返します。次に、heightForRowAtIndexPath
でそのメソッドを使用して、適切なサイズを返します。
また、これらの種類のviewControllerを埋め込むと、奇妙な動作が発生する可能性があることに注意してください。私は一度それをやったが、うまくいかなかった奇妙な記憶の問題を抱えていた。
私がこれまでに考え出した最も簡単なアプローチは、上記の@igorの答えへのクレジット、
あなたのtableviewcellクラスにこれを挿入してください
override func layoutSubviews() {
self.collectionViewOutlet.constant = self.postPoll.collectionViewLayout.collectionViewContentSize.height
}
そしてもちろん、セルのクラスのアウトレットでcollectionviewoutletを変更します
Pablo Romeuのソリューションの代替案は、Table View Cellで作業を行うのではなく、UICollectionView自体をカスタマイズすることです。
根本的な問題は、デフォルトではコレクションビューに固有のサイズがないため、使用するディメンションの自動レイアウトを通知できないことです。有用な固有のサイズを返すカスタムサブクラスを作成することで、これを改善できます。
UICollectionViewのサブクラスを作成し、次のメソッドをオーバーライドします
override func intrinsicContentSize() -> CGSize {
self.layoutIfNeeded()
var size = super.contentSize
if size.width == 0 || size.height == 0 {
// return a default size
size = CGSize(width: 600, height:44)
}
return size
}
override func reloadData() {
super.reloadData()
self.layoutIfNeeded()
self.invalidateIntrinsicContentSize()
}
(関連するメソッドreloadSections、reloadItemsAtIndexPathsもreloadData()と同様の方法でオーバーライドする必要があります)
LayoutIfNeededを呼び出すと、コレクションビューでコンテンツサイズが再計算され、これが新しい組み込みサイズとして使用できるようになります。
また、Table View Controllerでビューサイズの変更(デバイスの回転など)を明示的に処理する必要があります
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
@ Igor からアイデアを得て、Swiftでの私のプロジェクトのためにこれに時間をかけます
あなたのこれを過ぎて
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//do dequeue stuff
cell.frame = tableView.bounds
cell.layoutIfNeeded()
cell.collectionView.reloadData()
cell.collectionView.heightAnchor.constraint(equalToConstant: cell.collectionView.collectionViewLayout.collectionViewContentSize.height)
cell.layoutIfNeeded()
return cell
}
追加:セルのロード時にUICollectionViewが途切れる場合。
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//do dequeue stuff
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
return cell
}
私は最近同じ問題に直面していましたが、答えのほとんどすべての解決策を試してみましたが、いくつかはうまくいきましたが、他の人は@ PabloRomeアプローチに関する私の主な関心事ではありませんでした(コレクションビュー以外)高さおよび制約の高さを計算し、結果を返して自動レイアウトを正しく行う必要があります。コードで手動で計算するのは好きではありません。だからここに私のコードで手動の計算を行うことなく私のためにうまくいった解決策があります。
テーブルビューのcellForRow:atIndexPath
で次の操作を行います。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//do dequeue stuff
//initialize the the collection view data source with the data
cell.frame = CGRect.zero
cell.layoutIfNeeded()
return cell
}
ここで何が起こるかは、コレクションビューの高さが計算された後に、TableViewセルの高さを強制的に調整することだと思います。 (データソースにcollectionView日付を提供した後)
私はすべての答えを読みました。これはすべての場合に役立つようです。
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
collectionView.layoutIfNeeded()
collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width , height: 1)
return collectionView.collectionViewLayout.collectionViewContentSize
}
Pabloのソリューションは私にはあまりうまくいきませんでした。奇妙な視覚効果がありました(collectionViewが正しく調整されていません)。
うまくいったのは、layoutSubviews()
の実行中にcollectionViewの高さの制約を(NSLayoutConstraintとして)collectionView contentSizeに調整することでした。これは、セルに自動レイアウトが適用されるときに呼び出されるメソッドです。
// Constraint on the collectionView height in the storyboard. Priority set to 999.
@IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
// Method called by autolayout to layout the subviews (including the collectionView).
// This is triggered with 'layoutIfNeeded()', or by the viewController
// (happens between 'viewWillLayoutSubviews()' and 'viewDidLayoutSubviews()'.
override func layoutSubviews() {
collectionViewHeightConstraint.constant = collectionView.contentSize.height
super.layoutSubviews()
}
// Call `layoutIfNeeded()` when you update your UI from the model to trigger 'layoutSubviews()'
private func updateUI() {
layoutIfNeeded()
}