UITableViewCell内のUICollectionView —動的な高さ?


125

アプリケーション画面の1つでは、のUICollectionView内部に配置する必要がありUITableViewCellます。これUICollectionViewには動的な数のアイテムがあり、動的に計算する必要がある高さになります。しかし、埋め込まれたの高さを計算しようとして問題が発生していますUICollectionView

私たちの包括的なUIViewControllerはストーリーボードで作成され、自動レイアウトを利用しています。しかし、の高さにUITableViewCell基づいての高さを動的に増やす方法がわかりませんUICollectionView

誰かがこれを達成する方法についていくつかのヒントやアドバイスを与えることができますか?


達成しようとしているレイアウトについてもう少し詳しく教えてください。高さはUITableViewCell実際のテーブルビューとは異なりますか?UICollectionViewとの両方で垂直スクロールが必要ですかUITableView
apouche

3
UITableViewCellの高さは、テーブルビューセルの一部であるUICollectionViewの高さに基づいて、動的であると想定されています。UICollectionViewのレイアウトは4列で動的な行数です。したがって、100アイテムの場合、UICollectionViewには4列と25行があります。特定のセルの高さ(UITableViewのheightForRowAtIndexPathで示される)は、そのUICollectionViewのサイズに基づいて調整できる必要があります。それはもう少し明確ですか?
シャドウマン2014年

@シャドウマン、私にこの状況の解決策を提案してください
Ravi Ojha

回答:


126

右の答えはYES、あなたはCANこれを行います。

数週間前にこの問題に遭遇しました。思ったより簡単です。セルをNIB(またはストーリーボード)に入れて固定し、自動レイアウトですべての作業を実行します

次のような構造があるとします。

TableView

TableViewCell

CollectionView

CollectionViewCell

CollectionViewCell

CollectionViewCell

[...可変セル数または異なるセルサイズ]

解決策は、最初にcollectionViewCellのサイズを計算し、次にコレクションビューのcontentSizeを計算し、それをセルのサイズとして使用するように自動レイアウトに指示することです。これは「魔法をかける」UIViewメソッドです。

-(void)systemLayoutSizeFittingSize:(CGSize)targetSize 
     withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority 
           verticalFittingPriority:(UILayoutPriority)verticalFittingPriority

ここでは、TableViewCellのサイズを設定する必要があります。この場合、CollectionViewのcontentSizeです。

CollectionViewCell

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

TableViewCellは魔法を行います。これには、collectionViewへのアウトレットがあり、UICollectionViewFlowLayoutの見積もられアイテムサイズを使用して、collectionViewセルの自動レイアウトを有効にします。

次に、秘訣は、tableLayoutSizeFittingSize ...メソッドでtableViewセルのサイズを設定することです。(注:iOS8以降)

注:私はのtableViewのデリゲートセルの高さメソッドを使用しようとした-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath.but 手遅れ 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

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はこれを整理するのに役立ちました


4
うん。これは機能します。これは受け入れられる答えになるはずです。
csotiriou

1
どんな団体でも私にサンプルコードを提供できますか?私は..テーブルビューセルの内部で縦コレクションビュー、複数の画像を表示したい
ラヴィOjha

4
最後にこれは私のために働きました、しかし私は他のトリックをしなければなりませんでした:cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), 10000);これは長いcollectionViewで長いセルを「シミュレート」するので、collectionViewはそのすべてのセルのサイズを計算します。それ以外の場合、collectionViewは可視セルサイズのみを計算し、talbeViewCellの高さを誤って設定しました。
2016

20
でフレームの高さを最大に設定するとsystemLayoutSizeFittingSize、セルを再読み込みするときにすばやく問題が発生し、アプリがの後にスタックしているようself.collectionView.layoutIfNeeded()です。フレームの高さを最大の高さではなく1に設定して修正しました。誰かがこの問題でつまずいたら、それが役立つことを願っています。
タイタン2017

5
ここでは私のためにそれを修正するものだ-でsystemLayoutSizeFitting、現在の答えに示しているものの順序を逆に、その最初のことをcollectionView.layoutIfNeeded()実行し、その後、collectionView.frame = CGRect.init(origin: .zero, size: CGSize.init(width: targetSize.width, height: 1))
xaphod

101

私の解決策は@PabloRomeuによって提案されたものよりもはるかに単純だと思います。

ステップからアウトレット作成1. UICollectionViewUITableViewCell 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;

1
実際にこれを試しましたか?コレクションビューに高さの制約を設定することも、テーブルビューセルのマージンに固定することもできないようです。制約が少なすぎる(サイズがゼロの警告になる)か、制約が競合する(いずれかが無視される)かのいずれかになります。このソリューションの制約を設定する方法を説明してください。
デール、

2
うまくいきました。のcollectionViewすべての側面に制約を追加しUITableViewCell、設定tableView.estimatedRowHeight = 44;しますtableView.rowHeight = UITableViewAutomaticDimension;
Igor

3
これはチャンピオンのように機能します...シンプルで効率的...ありがとう。誰かが機能していない場合は、おそらくその理由かもしれません。コレクションビューの下部をテーブルビューのセルの下部にバインドする必要があります。それからそれは働き始めるでしょう。ハッピーコーディング.. :)
Sebin Roy

2
iPadを回転させるなどして画面のサイズを変更すると、これは機能しますか?これは、コレクションビューの高さ、したがってセルの高さを変更する可能性がありますが、画面上にある場合、セルは必ずしもテーブルビューによってリロードされるとは限らないため、cellForRowAtIndexPath:メソッドを呼び出さないでください。制約定数を変更します。ただし、reloadDataを強制することは可能です。
Carl Lindberg

2
別のcollectionViewセルのサイズを試しましたか?それが私の問題でした。別のcollectionViewのセルサイズがある場合、それは機能しません... :(
Pablo Romeu

21

テーブルビューとコレクションビューはどちらもUIScrollViewサブクラスであるため、コンテンツサイズの計算やセルの再利用などを行うため、別のスクロールビュー内に埋め込むことはできません。

すべての目的でコレクションビューのみを使用することをお勧めします。

これをセクションに分割し、一部のセクションのレイアウトをテーブルビューとして、他のセクションのレイアウトをコレクションビューとして「扱う」ことができます。結局のところ、コレクションビューでは、テーブルビューでは実現できないことは何もありません。

コレクションビューの「パーツ」の基本的なグリッドレイアウトがある場合は、通常のテーブルセルを使用してそれらを処理することもできます。それでもiOS 5のサポートが必要ない場合は、コレクションビューを使用することをお勧めします。


1
CollectionViewセクションごとに異なるレイアウトを定義できないので、「treat」を引用符で囲みましたね?コレクションビューセクションごとに異なるレイアウトを定義するオプションが見つかりません。UICollectionViewLayoutの独自のカスタムサブクラスのセクションごとに、「layoutAttributesForItemAtIndexPath」に対して異なる応答をすることについて、私は見落としているのですか、それとも話していますか?
アレックスダフランカ2015年

はい->あなたlayoutAttributesForItemAtIndexPathは私の独自のカスタムサブクラスのセクションごとに異なる応答をすることについて話していUICollectionViewLayoutますか?
Rivera

2
@リベラ、あなたの答えは最も合理的であり、正確に聞こえます。私は多くの場所でこの提案を読みました。また、サンプルへのリンクや、それを実現する方法、または少なくともいくつかの指針について教えてください。TIA
thesummersign 2015年

3
あなたの答えはあなたがゼロから始めることを想定しており、あなたがフィットする必要がある大きな複雑なテーブルビューをまだ持っていません。テーブルビューにコレクションビューを配置することについて具体的に尋ねられた質問。IMOあなたの応答は「答え」ではありません
xaphod

2
これは悪い答えです。テーブルビュー内にコレクションビューを配置するのはよくあることであり、ほとんどすべてのアプリで実行されます。
crashoverride777 2018年

9

上記のPablo Romeuの回答(https://stackoverflow.com/a/33364092/2704206)は、私の問題に非常に役立ちました。しかし、これを私の問題で機能させるには、いくつか異なることをしなければなりませんでした。まず、layoutIfNeeded()頻繁に電話する必要はありませんでした。私はそれを関数collectionViewで呼び出すだけで済みましたsystemLayoutSizeFitting

次に、テーブルビューセルのコレクションビューに自動レイアウト制約を設定して、パディングを追加しました。したがってtargetSize.widthcollectionView.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プロトコルを実装させることができます。これが他の誰かに役立つことを願っています!


5

私はすべての答えを読みました。これはすべてのケースに役立つようです。

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
}

3

Pablo Romeuのソリューションの代替手段は、テーブルビューセルで作業を行うのではなく、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を呼び出すと、コレクションビューでコンテンツサイズが再計算され、新しい固有のサイズとして使用できます。

また、テーブルビューコントローラーでビューサイズの変更(デバイスの回転時など)を明示的に処理する必要があります。

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    dispatch_async(dispatch_get_main_queue()) {
        self.tableView.reloadData()
    }
}

私の問題stackoverflow.com/questions/44650058/…を助けてくれませんか?
May Phyu

3

私は最近同じ問題に直面していて、答えのほとんどすべての解決策を試しました、それらのいくつかは機能し、他は@PabloRomeuアプローチについて私の主な懸念事項ではありませんでした(セル内に他のコンテンツがある場合(コレクションビュー以外))それらの高さと制約の高さを計算し、結果を返して自動レイアウトを正しくする必要があります。コードで手動で計算するのは好きではありません。だからここに私のコードで手動の計算を行うことなく私のためにうまくいった解決策があります。

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
}

コレクションビューの高さが計算された後、テーブルビューセルにセルの高さを強制的に調整することで、ここで何が起こるかと思います。(collectionViewの日付をデータソースに提供した後)


2

私はコレクションビュークラスに静的メソッドを配置し、コンテンツに基づいてサイズを返します。次に、でそのメソッドを使用してheightForRowAtIndexPath、適切なサイズを返します。

また、これらの種類のviewControllerを埋め込むと、奇妙な動作が発生する可能性があることに注意してください。私は一度それをやった、そして私が解決したことのない奇妙な記憶問題を抱えていた。


私にとっては、コレクションビューコントローラー内にテーブルビューコントローラーを埋め込もうとしていました。セルが再利用されるたびに、セルに適用したいくつかの自動レイアウト制約が記憶されてしまいました。それは非常に手間がかかり、最終的にはひどい考えでした。代わりに単一のコレクションビューを使用すると思います。
fatuhoku 2015年

2

多分私の亜種は役に立つでしょう。過去2時間にこのタスクを決定しました。100%正確または最適であるとは思いませんが、私のスキルはまだ非常に低いので、専門家からのコメントを聞きたいです。ありがとうございました。重要な注意点:これは静的テーブルで機能します-現在の作業で指定されています。したがって、私が使用するのはtableViewのviewWillLayoutSubviewsだけです。そしてもう少し。

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
    }
}
  1. 私は、collectionViewが2番目のセクションの最初の行にあることを知っています。
  2. 行の高さを17 pとします。コンテンツの高さよりも大きい。
  3. iconsCellHeightは、プログラムの開始時の乱数です(ポートレイト形式では正確に392でなければなりませんが、重要ではありません)。collectionView + 17のコンテンツがこの数と等しくない場合は、その値を変更します。次回この状況では、条件はFALSEを返します。
  4. 結局、tableViewを更新します。私の場合は、2つの操作の組み合わせです(拡張する行を適切に更新するため)。
  5. そしてもちろん、heightForRowAtIndexPathに1行をコードに追加します。

2

私がこれまでに思いついた最も簡単なアプローチは、上記の@igor回答へのクレジット、

あなたのtableviewcellクラスにこれを挿入するだけです

override func layoutSubviews() {
    self.collectionViewOutlet.constant = self.postPoll.collectionViewLayout.collectionViewContentSize.height
}

そしてもちろん、セルのクラスのコンセントでcollectionviewoutletを変更します


1
セルの高さが正しく計算されない
Nik Kov

2

@Igorの投稿からアイデアを得て、迅速に私のプロジェクトのためにこれに時間を費やしています

あなたのこれをちょうど過ぎて

 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
}

0

Pabloのソリューションはうまく機能しませんでした。奇妙な視覚効果がありました(collectionViewが正しく調整されていません)。

機能したのは、の間にcollectionViewの高さ制約を(NSLayoutConstraintとして)collectionView contentSizeに調整することでしたlayoutSubviews()。これは、セルに自動レイアウトが適用されたときに呼び出されるメソッドです。

// 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()
}

-3

UITableViewDelegateで:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return ceil(itemCount/4.0f)*collectionViewCellHeight;
}

itemCountとCollectionViewCellHeightを実際の値に置き換えます。配列の配列がある場合、itemCountは次のようになります。

self.items[indexPath.row].count

または何でも。


主な質問は、collectionViewCellHeight レンダリングする前にどのように計算するかです。
thesummersign 2015年

-3

1.ダミーセルを作成します。
2.現在のデータを使用して、UICollectionViewのUICollectionViewLayoutでcollectionViewContentSizeメソッドを使用します。


これは、ダミーセルにコンテンツを入れずに「uicollectionview」で、セルの高さを設定する可能性がないことから、高さを取得する...最高の答えは、その後、適切なセルに再設定する必要があります
パルトロミエSemańczyk

その方法はどのように使用しますか?
xtrinch 2016年

例を追加します。
Nik Kov 2018年

-5

あなたのような、そのプロパティに基づいて、コレクションの高さを計算することができitemSizesectionInsetminimumLineSpacingminimumInteritemSpacingあなたの場合には、collectionViewCellルールの境界線を持っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.