FlowLayoutでUICollectionViewの高さを決定する方法


118

私が持っているUICollectionViewとしUICollectionViewFlowLayout、と私は(に復帰するために、そのコンテンツのサイズを計算するintrinsicContentSize自動レイアウトを経由してその高さを調整するために必要)。

問題は次のとおりです。すべてのセルの高さが固定されていても等しい場合でも、に「行」/行の数がわかりませんUICollectionView。また、データアイテムを表すセルの幅が異なり、結果としての1行にあるアイテムの数も異なるため、データソースのアイテムの数でその数を判断することもできませんUICollectionView

公式ドキュメントでこのトピックに関するヒントを見つけることができなかったため、グーグル操作でそれ以上何もできなかったので、助けやアイデアをいただければ幸いです。

回答:


255

うわあ!何らかの理由で、何時間もの調査の結果、質問に対するかなり簡単な答えが見つかりましたUICollectionView。間違った場所で完全に検索し、見つけたすべてのドキュメントを調べていました。

シンプルで簡単なソリューションは、基本的なレイアウトcollectionViewContentSizeにあります。myCollectionView.collectionViewLayoutプロパティを呼び出すだけで、コンテンツの高さと幅をとして取得できますCGSize。それはそれと同じくらい簡単です。


1
おっと、UITableViewに似たようなものがあるといいのですが
Chris Chen

1
Ignatus、@ jbandi、Chris他、これについて詳しく説明していただけますか?(項目が水平にレイアウトされるように)項目間の間隔が広い水平スクロール方向でテストすると、コレクションビューは無効な組み込みコンテンツサイズ(-1、-1)を返し、collectionViewContentSizeは効果的にコレクションビューの境界-スペースで囲まれた行が1つしかないことを考慮していません。要するに、それはあまり本質的ではありませんでした。ありがとう!
Chris Conover、2014年

8
スクロールビューのcollectionViewContentSizeとcontentSizeの違いは何ですか?
15年

viewDidLoadでコレクションビューのcontentSizeを呼び出すと、コレクションビューのフレームが返され、collectionViewContentSizeは正しいコンテンツサイズを返します。
フューリー

1
だれのために役立つか:これを機能させるために、この前に "layoutIfNeeded()"を実行する必要がありました。
asanli

37

自動レイアウトを使用している場合は、次のサブクラスを作成できますUICollectionView

以下のコードを使用する場合、コレクションビューのコンテンツに基づいて変化するため、コレクションビューの高さの制約を指定する必要はありません。

以下に実装を示します。

@interface DynamicCollectionView : UICollectionView

@end

@implementation DynamicCollectionView

- (void) layoutSubviews
{
    [super layoutSubviews];

    if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize]))
    {
        [self invalidateIntrinsicContentSize];
    }
}

- (CGSize)intrinsicContentSize
{
    CGSize intrinsicContentSize = self.contentSize;

    return intrinsicContentSize;
}

@end

12
うまくいきませんでした... UITableViewCell内でCollectionViewを使用していますが、iOS8 UITableViewAutomaticDimensionを使用してコレクションビューの高さが高くなるにつれて、テーブルビューの高さを高くしたいと思っていますが、コレクションビューは小さいままです:(
Georg

驚くばかり。UIScrollView内の私のUICollectionViewの短くて甘いソリューション!
dotToString

2
1つの重要なことは、コレクションビューでスクロールとスクロールバーを無効にすることです
Georg

1
Georgと同じ問題で、400を超えるはずの高さは約50pxです
xtrinch

3
はい、collectionviewを「scrollabeではなく、scollbarsなし」に設定してからリロードすると、テーブルビューのコンテンツが設定されます。また、collectionviewは、そのコンテンツサイズを組み込みコンテンツサイズとして返すサブクラスです。お役に立てば幸いです。
Georg

25

で、viewDidAppearあなたはそれをすることによって得ることができます:

float height = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;

多分あなたがデータをリロードしてから新しいデータで新しい高さを計算する必要があるときそれを得ることができます:でCollectionViewデータをリロードしたときにリッスンするオブザーバーを追加しますviewdidload

[self.myCollectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

次に、bellow関数を追加して、新しい高さを取得するか、collectionviewのリロードが完了した後に何かを行います。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    //Whatever you do here when the reloadData finished
    float newHeight = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;    
}

そして、オブザーバーを削除することを忘れないでください:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];

11

user1046037 Swiftで回答...

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize() {
            invalidateIntrinsicContentSize()
        }
    }

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}

それは完全に動作しますが、UILabel上記のようなものがある場合、コンテンツがcollectionView上記の他の要素を通過した場合、別の解決策がありますか?
KolaCaine

11

user1046037回答のSwift 3コード

import UIKit

class DynamicCollectionView: UICollectionView {

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
            self.invalidateIntrinsicContentSize()
        }

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}

私はより新鮮で、私のViewControllerでこれをどこでどのように使用するかわからない、答えを編集してください.....?
Abirami Bala

1
ストーリーボードのコレクション
ビューに

7

スウィフト4

class DynamicCollectionView: UICollectionView {
  override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
      self.invalidateIntrinsicContentSize()
    }
  }

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}

1
iPhone 6/7 PlusおよびXR、XS-MAXでは機能しないことがわかりました
Rahul K Rajan

7

Swift 4.2

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}

4

この質問に答えるのは遅すぎますが、私は最近この問題を経験しました。
@leeの上記の回答は、私の回答を得るのに非常に役立ちます。しかし、その答えはobjective-cに限定されており、私は迅速に作業していました。
@leeあなたの答えを参考にして、迅速なユーザーがこの問題を簡単に解決できるようにしましょう。そのためには、以下の手順に従ってください:

宣言CGFloatセクションで変数を宣言します。

var height : CGFloat!

で、viewDidAppearあなたはそれをすることによって得ることができます:

height = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

あなたはときたぶんreloadデータは、新しいデータを使用して新しい高さを計算する必要がある、あなたはでそれを得ることができます:addObserverあなたの聴くことCollectionViewで、完成リロードデータをviewWillAppear

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(true)
        ....
        ....
        self.shapeCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old, context: nil)
    }

次に、ベロー関数を追加して、新しい高さを取得するか、collectionviewリロードの完了後に何かを行います。

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let newHeight : CGFloat = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

        var frame : CGRect! = self.myCollectionView.frame
        frame.size.height = newHeight

        self.myCollectionView.frame = frame
    }

そして、オブザーバーを削除することを忘れないでください:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    ....
    ....
    self.myCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

これが問題の迅速な解決に役立つことを願っています。


@ShikhaSharma:「ViewDidDisappear」メソッド内にremoveObserverコードを追加できます。これにより、ビューが消えたことを確認した後にのみオブザーバーが削除されます。更新された回答を確認してください。
えー ビハール

このエラーが発生しています:-observeValueForKeyPath:ofObject:change:context:メッセージが受信されましたが、処理されませんでした。
Shikha Sharma

@ShikhaSharma:すみません、メソッドを指定するのは私の間違いです。viewWillDisappearにremoveobserverコードを配置してみてください。
えー ビハール

1

この回答は@Erに基づいています。ビハールの答え。RxSwiftを使用してソリューションを簡単に実装できます

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
        print("sizer \(size)")
    }).disposed(by: rx.disposeBag)

0

@ user1046037のSwiftバージョン:

public class DynamicCollectionView: UICollectionView {

    override public func layoutSubviews() {
        super.layoutSubviews()
        if !bounds.size.equalTo(intrinsicContentSize) {
            invalidateIntrinsicContentSize()
        }
    }

    override public var intrinsicContentSize: CGSize {
        let intrinsicContentSize: CGSize = contentSize
        return intrinsicContentSize
    }
}

0

Swift 4.2、Xcode 10.1、iOS 12.1

何らかの理由collectionView.contentSize.heightで、コレクションビューの解決された高さよりも小さく見えました。まず、スーパービューの高さの1/2を基準にして自動レイアウト制約を使用していました。これを修正するために、ビューの「安全領域」を基準にして制約を変更しました。

これにより、次のコマンドを使用して、セルの高さを設定してコレクションビューを満たすことができますcollectionView.contentSize.height

private func setCellSize() {
    let height: CGFloat = (collectionView.contentSize.height) / CGFloat(numberOfRows)
    let width: CGFloat = view.frame.width - CGFloat(horizontalCellMargin * 2)

    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSize(width: width, height: height)
}

スーパービューに対する高さの制限 シミュレータの結果が悪い

安全領域に対する高さの制約 良いシミュレーター結果


0

仮定すると、あなたのcollectionViewは次のように命名されcollectionView次のように高さを取ることができます。

let height = collectionView.collectionViewLayout.collectionViewContentSize.height

0

さらにreloadData、高さを取得する前に呼び出すほうがよいでしょう。

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