So bestimmen Sie die Höhe von UICollectionView mit FlowLayout

118

Ich habe eine UICollectionViewmit einerUICollectionViewFlowLayout und möchte dessen Inhaltsgröße berechnen (für die Rückgabe, intrinsicContentSizedie zum Anpassen der Höhe über AutoLayout erforderlich ist).

Das Problem ist: Selbst wenn ich eine feste und gleiche Höhe für alle Zellen habe, weiß ich nicht, wie viele "Zeilen" / Zeilen ich in der habe UICollectionView. Ich kann diese Anzahl auch nicht anhand der Anzahl der Elemente in meiner Datenquelle bestimmen, da die Zellen, die die Datenelemente darstellen, unterschiedlich breit sind und folglich auch die Anzahl der Elemente in einer Zeile der UICollectionView.

Da ich in der offiziellen Dokumentation keine Hinweise zu diesem Thema finden konnte und das Googeln mich nicht weiter brachte, wären Hilfe und Ideen sehr willkommen.

Ignatius Tremor
quelle

Antworten:

255

Whoa! Aus irgendeinem Grund fand ich nach stundenlanger Recherche jetzt eine ziemlich einfache Antwort auf meine Frage: Ich suchte völlig am falschen Ort und stöberte in allen Unterlagen, die ich finden konnte UICollectionView.

Die einfache Lösung liegt im zugrunde liegenden Layout: Rufen collectionViewContentSizeSie einfach Ihre myCollectionView.collectionViewLayoutImmobilie an und Sie erhalten die Höhe und Breite des Inhalts als CGSize. So einfach ist das.

Ignatius Tremor
quelle
1
whoa, ich wünschte, UITableView hat etwas ähnliches
Chris Chen
1
Ignatus, @jbandi, Chris et al., Können Sie das näher erläutern? Beim Testen mit einer horizontalen Bildlaufrichtung mit einem großen Abstand zwischen den Elementen (so dass die Elemente horizontal angeordnet sind) gab die Sammlungsansicht eine ungültige intrinsische Inhaltsgröße (-1, -1) zurück, und collectionViewContentSize gab das zurück, was effektiv war die Grenzen der Sammlungsansicht - ohne zu berücksichtigen, dass nur eine Zeile vom Leerzeichen umgeben war. Kurz gesagt, es war nicht sehr intrinsisch. Vielen Dank!
Chris Conover
8
Was ist der Unterschied zwischen collectionViewContentSize und contentSize in der Bildlaufansicht?
Rounak
Wenn Sie contentSize der Sammlungsansicht in viewDidLoad aufrufen, wird der Frame der Sammlungsansicht zurückgegeben. CollectionViewContentSize gibt die korrekte Inhaltsgröße zurück.
Wut
1
Für wen es helfen kann: Ich musste vorher ein "layoutIfNeeded ()" machen, damit es funktioniert.
Asanli
37

Wenn Sie das automatische Layout verwenden , können Sie eine Unterklasse von erstellenUICollectionView

Wenn Sie den folgenden Code verwenden, müssen Sie keine Höhenbeschränkungen für die Sammlungsansicht angeben, da diese je nach Inhalt der Sammlungsansicht variieren würden.

Im Folgenden ist die Implementierung angegeben:

@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
user1046037
quelle
11
hat bei mir nicht funktioniert ... Ich verwende meine CollectionView in einer UITableViewCell und möchte, dass die Tabellenansicht mit iOS8 UITableViewAutomaticDimension an Höhe zunimmt, wenn die Sammlungsansicht an Höhe zunimmt. Meine Sammlungsansicht bleibt jedoch klein :(
Georg
Genial. Kurze und süße Lösung für meine UICollectionView in einer UIScrollView!
dotToString
2
Eine wichtige Sache ist, das Scrollen und die Bildlaufleisten in der Sammlungsansicht zu deaktivieren
Georg
1
Gleiches Problem wie Georg, es ist ungefähr 50px hoch, wenn es mehr als 400 sein sollte
xtrinch
3
Ja, ich habe die Sammlungsansicht auf "Nicht scrollabe, keine Bildlaufleisten" gesetzt und sie dann neu geladen, dann wird der Inhalt der Tabellenansicht festgelegt. Die Sammlungsansicht ist auch eine Unterklasse, die ihre Inhaltsgröße als intrinsicContentSize zurückgibt. Hoffentlich hilft das!
Georg
25

An viewDidAppearkönnen Sie es bekommen durch:

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

Wenn Sie Daten neu laden und dann eine neue Höhe mit neuen Daten berechnen müssen, können Sie diese möglicherweise erhalten, indem Sie: Beobachter hinzufügen, um zu hören, wann Ihre CollectionViewDaten neu geladen wurden viewdidload:

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

Fügen Sie dann die folgende Funktion hinzu, um eine neue Höhe zu erhalten, oder führen Sie nach dem erneuten Laden der Sammlungsansicht eine Aktion aus:

- (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;    
}

Und vergessen Sie nicht, den Beobachter zu entfernen:

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

user1046037 Antwort in Swift ...

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

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
Nick Wood
quelle
Es funktioniert perfekt, aber wenn Sie etwas wie UILabeloben haben, der Inhalt von collectionViewüber ein anderes Element oben geht, haben Sie eine andere Lösung?
KolaCaine
11

Swift 3-Code für die Antwort von user1046037

import UIKit

class DynamicCollectionView: UICollectionView {

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

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}
Mohammad Sadiq Shaikh
quelle
Ich bin frischer und nicht sicher, wo und wie ich dies in meinem ViewController verwenden soll. Können Sie bitte eine Bearbeitung mit der Antwort vornehmen .....?
Abirami Bala
1
Stellen Sie diese Klasse einfach auf Ihre Sammlungsansicht in Storyboard
Mohammad Sadiq Shaikh
7

Swift 4

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

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}
Wilson
quelle
1
Ich fand, dass es nicht in iPhone 6/7 plus und 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
    }
}
Cesare
quelle
4

Es ist zu spät, um diese Frage zu beantworten, aber ich habe dieses Problem kürzlich durchgearbeitet.
Die obige Antwort von @ lee hilft mir sehr, meine Antwort zu bekommen. Aber diese Antwort beschränkt sich auf Ziel-c und ich habe schnell gearbeitet .
@lee Erlauben Sie mir unter Bezugnahme auf Ihre Antwort, den schnellen Benutzer dieses Problem leicht lösen zu lassen. Befolgen Sie dazu bitte die folgenden Schritte:

Deklarieren Sie eine CGFloatVariable im Deklarationsabschnitt:

var height : CGFloat!

An viewDidAppearkönnen Sie es bekommen durch:

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

Wenn Ihre reloadDaten dann eine neue Höhe mit neuen Daten berechnen müssen, können Sie diese möglicherweise abrufen, indem Sie Folgendes addObserverabhören: Wenn Ihre CollectionViewDaten nach dem erneuten Laden fertig sind viewWillAppear:

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

Fügen Sie dann die folgende Funktion hinzu, um eine neue Höhe zu erhalten, oder führen Sie nach dem erneuten collectionviewLaden etwas aus:

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
    }

Und vergessen Sie nicht, den Beobachter zu entfernen:

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

Ich hoffe, dies wird Ihnen helfen, Ihr Problem schnell zu lösen.

Er. Vihar
quelle
@ShikhaSharma: Sie können den removeObserver-Code innerhalb der "ViewDidDisappear" -Methode hinzufügen, wodurch der Beobachter erst entfernt wird, nachdem die Ansicht bestätigt hat, dass er verschwunden ist. Bitte überprüfen Sie die aktualisierte Antwort.
Ähm. Vihar
Ich erhalte die folgende Fehlermeldung: Ein -observeValueForKeyPath: ofObject: change: context: message wurde empfangen, aber nicht behandelt.
Shikha Sharma
@ ShikhaSharma: Entschuldigung, es ist mein Fehler, die Methode anzugeben. Versuchen Sie, removeobserver-Code in viewWillDisappear einzufügen.
Ähm. Vihar
1

Diese Antwort basiert auf @Er. Vihars Antwort. Sie können die Lösung einfach mit RxSwift implementieren

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

Schnelle Version von @ user1046037:

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
    }
}
Joshua Hart
quelle
0

Swift 4.2, Xcode 10.1, iOS 12.1:

Aus irgendeinem Grund collectionView.contentSize.heighterschien es kleiner als die aufgelöste Höhe meiner Sammlungsansicht. Zuerst habe ich eine automatische Layoutbeschränkung in Bezug auf die Hälfte der Höhe der Übersicht verwendet. Um dies zu beheben, habe ich die Einschränkung so geändert, dass sie relativ zum "sicheren Bereich" der Ansicht ist.

Dadurch konnte ich die Zellenhöhe so einstellen, dass meine Sammlungsansicht mit collectionView.contentSize.heightfolgenden Elementen gefüllt wurde :

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

Vor

Höhenbeschränkung relativ zur Übersicht schlechtes Simulatorergebnis

Nach dem

Höhenbeschränkung relativ zum sicheren Bereich gutes Simulatorergebnis

Andrew Kirna
quelle
0

Unter der Annahme, dass Ihre collectionView als collectionView bezeichnet wird , können Sie die Höhe wie folgt annehmen.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height
Pramodya Abeysinghe
quelle
0

Außerdem , rufen Sie besser an, reloadDatabevor Sie die Höhe ermitteln:

theCollectionView.collectionViewLayout.collectionViewContentSize;
guozqzzu
quelle