UICollectionView Self Sizing Cells mit automatischem Layout


207

Ich versuche, die Selbstgröße UICollectionViewCellsmit Auto Layout zu erreichen, aber ich kann die Zellen scheinbar nicht dazu bringen, sich an den Inhalt anzupassen. Ich habe Probleme zu verstehen, wie die Größe der Zelle anhand des Inhalts der Inhaltsansicht der Zelle aktualisiert wird.

Hier ist das Setup, das ich versucht habe:

  • Benutzerdefiniert UICollectionViewCellmit einem UITextViewin seiner Inhaltsansicht.
  • Das Scrollen nach UITextViewist deaktiviert.
  • Die horizontale Einschränkung von contentView lautet: "H: | [_textView (320)]", dh die UITextViewwird links von der Zelle mit einer expliziten Breite von 320 angeheftet .
  • Die vertikale Einschränkung von contentView lautet: "V: | -0 - [_ textView]", dh die UITextViewam oberen Rand der Zelle angeheftete .
  • Für UITextViewhat eine Höhenbeschränkung auf eine Konstante festgelegt, deren UITextViewBerichte in den Text passen.

So sieht es aus, wenn der Zellenhintergrund auf Rot und der UITextViewHintergrund auf Blau eingestellt ist: Zellenhintergrund rot, UITextView-Hintergrund blau

Ich habe das Projekt, mit dem ich gespielt habe, hier auf GitHub gestellt .


Implementieren Sie die sizeForItemAtIndexPath-Methode?
Daniel Galasko

@ DanielGalasko bin ich nicht. Meines Wissens nach müssen Sie dies für die neue Funktion zur Selbstgröße von Zellen in iOS 8 nicht mehr tun.
Rawbee


Aber auch dies hängt von Ihrem Anwendungsfall ab.
Stellen

1
2019 - Es ist wichtig, dies zu betrachten: stackoverflow.com/a/58108897/294884
Fattie

Antworten:


309

Aktualisiert für Swift 5

preferredLayoutAttributesFittingAttributesumbenannt in preferredLayoutAttributesFittingund verwenden Sie die automatische Größenanpassung


Aktualisiert für Swift 4

systemLayoutSizeFittingSize umbenannt in systemLayoutSizeFitting


Aktualisiert für iOS 9

Nachdem ich gesehen hatte, dass meine GitHub-Lösung unter iOS 9 kaputt ging, hatte ich endlich die Zeit, das Problem vollständig zu untersuchen. Ich habe das Repo jetzt aktualisiert und einige Beispiele für verschiedene Konfigurationen für Zellen mit Selbstgröße hinzugefügt. Mein Fazit ist, dass Zellen mit Selbstgröße theoretisch großartig, in der Praxis jedoch chaotisch sind. Ein Wort der Vorsicht, wenn Sie mit Zellen mit Selbstgröße fortfahren.

TL; DR

Schauen Sie sich mein GitHub-Projekt an


Zellen mit Selbstgröße werden nur mit Ablauflayout unterstützt. Stellen Sie daher sicher, dass Sie diese verwenden.

Es gibt zwei Dinge, die Sie einrichten müssen, damit Zellen mit Selbstgröße funktionieren.

1. Stellen Sie estimatedItemSizeaufUICollectionViewFlowLayout

Das Flow-Layout wird von Natur aus dynamisch, sobald Sie die estimatedItemSizeEigenschaft festlegen .

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Fügen Sie Unterstützung für die Größenbestimmung in Ihrer Zellunterklasse hinzu

Dies kommt in 2 Geschmacksrichtungen; Auto-Layout oder benutzerdefinierte Überschreibung von preferredLayoutAttributesFittingAttributes.

Erstellen und konfigurieren Sie Zellen mit Auto Layout

Ich werde nicht näher darauf eingehen, da es einen brillanten SO-Beitrag zum Konfigurieren von Einschränkungen für eine Zelle gibt. Seien Sie nur vorsichtig, dass Xcode 6 mit iOS 7 eine Reihe von Problemen gelöst hat. Wenn Sie also iOS 7 unterstützen, müssen Sie beispielsweise sicherstellen, dass die Autoresizing-Maske in der Inhaltsansicht der Zelle festgelegt ist und dass die Grenzen der Inhaltsansicht als die Grenzen der Zelle festgelegt werden, wenn Die Zelle wird geladen (dh awakeFromNib).

Was Sie beachten müssen, ist, dass Ihre Zelle stärker eingeschränkt werden muss als eine Zelle mit Tabellenansicht. Wenn Sie beispielsweise möchten, dass Ihre Breite dynamisch ist, benötigt Ihre Zelle eine Höhenbeschränkung. Wenn Sie möchten, dass die Höhe dynamisch ist, benötigen Sie ebenfalls eine Breitenbeschränkung für Ihre Zelle.

Implementieren Sie preferredLayoutAttributesFittingAttributesin Ihrer benutzerdefinierten Zelle

Wenn diese Funktion aufgerufen wird, wurde Ihre Ansicht bereits mit Inhalten konfiguriert (dh cellForItemaufgerufen). Unter der Annahme, dass Ihre Einschränkungen angemessen festgelegt wurden, könnten Sie eine Implementierung wie diese haben:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

HINWEIS Unter iOS 9 hat sich das Verhalten etwas geändert, was zu Abstürzen Ihrer Implementierung führen kann, wenn Sie nicht vorsichtig sind (weitere Informationen finden Sie hier ). Bei der Implementierung müssen preferredLayoutAttributesFittingAttributesSie sicherstellen, dass Sie den Rahmen Ihrer Layoutattribute nur einmal ändern. Wenn Sie dies nicht tun, ruft das Layout Ihre Implementierung auf unbestimmte Zeit auf und stürzt schließlich ab. Eine Lösung besteht darin, die berechnete Größe in Ihrer Zelle zwischenzuspeichern und diese jedes Mal ungültig zu machen, wenn Sie die Zelle wiederverwenden oder ihren Inhalt ändern, wie ich es mit der isHeightCalculatedEigenschaft getan habe .

Erleben Sie Ihr Layout

Zu diesem Zeitpunkt sollten in Ihrer collectionView "funktionierende" dynamische Zellen vorhanden sein. Ich habe die Out-of-the-Box-Lösung während meiner Tests noch nicht als ausreichend befunden. Wenn Sie dies getan haben, können Sie dies gerne kommentieren. Es fühlt sich immer noch so an, als würde UITableViewder Kampf um die dynamische Dimensionierung IMHO gewonnen.

Vorsichtsmaßnahmen

Beachten Sie sehr, dass bei Verwendung von Prototypzellen zur Berechnung der geschätzten Artikelgröße dies unterbrochen wird, wenn Ihre XIB Größenklassen verwendet . Der Grund dafür ist, dass beim Laden Ihrer Zelle von einem XIB die Größenklasse mit konfiguriert wird Undefined. Dies wird nur unter iOS 8 und höher unterbrochen, da unter iOS 7 die Größenklasse basierend auf dem Gerät geladen wird (iPad = Regular-Any, iPhone = Compact-Any). Sie können entweder die geschätzte Größe festlegen, ohne die XIB zu laden, oder Sie können die Zelle aus der XIB laden, sie zur collectionView hinzufügen (dies legt die traitCollection fest), das Layout ausführen und sie dann aus der Übersicht entfernen. Alternativ können Sie auch festlegen, dass Ihre Zelle den traitCollectionGetter überschreibt und die entsprechenden Merkmale zurückgibt. Es liegt an dir.

Lassen Sie mich wissen, wenn ich etwas verpasst habe, hoffe ich habe geholfen und viel Glück beim Codieren



2
Ich versuche, dasselbe mit dem Flow-Layout zu implementieren. Wenn ich jedoch newFrame mit aktualisierter Größe zurückgebe, scheint das Layout die y-Positionen nicht neu zu berechnen, die immer noch auf der Höhe in geschätzter Größe basieren. Was fehlt?
Anna

@anna rufst du invalidateLayout im Layout an?
Daniel Galasko

1
Ah, fand den Unterschied, Sie haben die geschätzte Höhe ziemlich hoch eingestellt (400), während ich das Minimum gemacht habe (44). Das hat geholfen. Aber die contentSize scheint immer noch nicht richtig eingestellt zu sein, bis Sie den ganzen Weg durchblättern, also immer noch nicht sehr brauchbar. Übrigens habe ich UITextView in UILabel geändert, dasselbe Verhalten.
Anna

17
Dies scheint unter iOS 9 GM grundlegend gebrochen zu sein. Das Einstellen estimatedItemSizeführt zu Abstürzen - es scheint einen großen Fehler zu geben, wie das automatische Layout versucht, die UICollectionViewCell zu verarbeiten.
Wes Campaigne

3
Hat jemand eine Lösung gefunden, die auf ein ähnliches Problem stößt?
Tony

47

In iOS10 gibt es eine neue Konstante namens UICollectionViewFlowLayout.automaticSize(früher UICollectionViewFlowLayoutAutomaticSize), also stattdessen:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

Sie können dies verwenden:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Es bietet eine bessere Leistung, insbesondere wenn die Zellen in Ihrer Sammlungsansicht eine konstante Breite haben

Zugriff auf das Flow-Layout:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Swift 5 Aktualisiert:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}

8
Wo stellst du das ein? in viewdidload? Wie bekommen Sie das Flowlayout in den Griff?
UKDataGeek

1
Ich habe es geschafft, dies zu implementieren - aber es hat ein seltsames Verhalten, das es veranlasst, die Zellen zu zentrieren, wenn es eine einzelne Zelle gibt. Hier ist die Frage zum Stapelüberlauf - hat mich verblüfft
UKDataGeek

1
Sie können über die collectionViews auf das Flow-Layout zugreifen collectionViewLayoutund überprüfen, ob es vom Typ istUICollectionViewFlowLayout
d4Rk

4
es macht meine Zellen sehr klein, nutzlos
user924

44

Ein paar wichtige Änderungen an Daniel Galaskos Antwort haben alle meine Probleme behoben. Leider habe ich (noch) nicht genug Ruf, um direkt zu kommentieren.

Fügen Sie in Schritt 1 bei Verwendung des automatischen Layouts einfach eine übergeordnete UIView zur Zelle hinzu. ALLES in der Zelle muss eine Unteransicht des übergeordneten Elements sein. Das hat alle meine Probleme beantwortet. Während Xcode dies für UITableViewCells automatisch hinzufügt, ist dies für UICollectionViewCells nicht der Fall (sollte es aber sein). Laut den Dokumenten :

Fügen Sie zum Konfigurieren des Erscheinungsbilds Ihrer Zelle die Ansichten hinzu, die erforderlich sind, um den Inhalt des Datenelements als Unteransichten zur Ansicht in der contentView-Eigenschaft darzustellen. Fügen Sie der Zelle selbst keine Unteransichten hinzu.

Überspringen Sie dann Schritt 3 vollständig. Es wird nicht benötigt.


1
Interessant; Dies ist speziell für Xibs, aber trotzdem danke, werde versuchen, mit dem Github-Projekt durcheinander zu kommen und zu sehen, ob ich reproduzieren kann. Vielleicht teilen Sie die Antwort in Xib vs programmatic
Daniel Galasko

Sehr hilfreich . Vielen Dank!
ProblemSlover

2
iOS 9, Xcode 7. Zellen werden im Storyboard protoypiert, benutzerdefinierte Unterklasse festgelegt. Beim Versuch, eine Eigenschaft namens zu erstellen contentView, beschwert sich Xcode über Konflikte mit vorhandenen Eigenschaften. Es wurde versucht, Unteransichten hinzuzufügen self.contentViewund Einschränkungen festzulegen , dann stürzt die App ab.
Nicolas Miari

@NicolasMiari Ich weiß nicht, wie ich das programmgesteuert machen soll. Meine Lösung besteht darin, nur eine einzige Ansicht in der XIB hinzuzufügen, in der alles enthalten ist. Sie müssen keine Eigenschaft oder etwas erstellen.
Matt Koala

Ich habe das gleiche Problem wie @NicolasMiari. Als ich die geschätzte Inhaltsgröße für Zellen festlegte, die im Storyboard mit Autolayout-Einschränkungen unter iOS 9 / Xcode 7 als Prototyp erstellt wurden, stürzt die App mit einer Ausnahme für schlechten Zugriff und ohne hilfreiche Stapelverfolgung ab
wasabi

34

In iOS 10+ ist dies ein sehr einfacher 2-Schritt-Prozess.

  1. Stellen Sie sicher, dass alle Ihre Zelleninhalte in einem einzelnen UIView (oder in einem Nachkommen von UIView wie UIStackView, das das automatische Layout erheblich vereinfacht) platziert sind. Genau wie bei der dynamischen Größenänderung von UITableViewCells müssen für die gesamte Ansichtshierarchie Einschränkungen konfiguriert werden, vom äußersten Container bis zur innersten Ansicht. Dies schließt Einschränkungen zwischen der UICollectionViewCell und der unmittelbaren untergeordneten Ansicht ein

  2. Weisen Sie das Flowlayout Ihres UICollectionView an, die Größe automatisch anzupassen

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Ich bin neugierig, wie würde das Verpacken des Inhalts Ihrer Zellen in eine UIView die Leistung des automatischen Layouts verbessern? Ich hätte gedacht, dass es mit einer flacheren Hierarchie besser abschneiden würde?
James Heald

1
Ich habe die Leistung nicht erwähnt, nur die Einfachheit. Zellen mit Selbstgröße funktionieren nur, wenn Sie Einschränkungen haben, die sich zwischen links und rechts sowie zwischen oben und unten erstrecken. Dies ist am einfachsten, wenn alle Ansichten in einem einzigen Container verpackt sind. Wenn dieser Container eine UIStackView ist, ist es einfacher, als wenn es sich um einen anderen Nachkommen von UIView handelt.
Marmoy

Können Sie mir bitte sagen, wie die Höhenkonstante für UICollectionViewFlowLayoutAutomaticSize festgelegt wird (z. B. die eingestellte Höhe 25 und die Breite werden automatisch geändert)?
ZhengGe Che

Mit Swift 4.1 teilt mir Xcode mit, dass ich UICollectionViewFlowLayout.automaticSizein umbenannt wurde UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth

14
  1. Fügen Sie flowLayout in viewDidLoad () hinzu

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Legen Sie außerdem eine UIView als mainContainer für Ihre Zelle fest und fügen Sie alle erforderlichen Ansichten hinzu.

  3. Weitere Informationen finden Sie in diesem fantastischen, umwerfenden Tutorial: UICollectionView mit Autosize-Zelle mithilfe von Autolayout in iOS 9 und 10


11

EDIT 19.11.19: Verwenden Sie für iOS 13 einfach UICollectionViewCompositionalLayout mit geschätzten Höhen. Verschwenden Sie keine Zeit mit dieser kaputten API.

Nachdem ich einige Zeit damit zu kämpfen hatte, stellte ich fest, dass die Größenänderung für UITextViews nicht funktioniert, wenn Sie das Scrollen nicht deaktivieren:

let textView = UITextView()
textView.scrollEnabled = false

Wenn ich versuche zu scrollen, ist die Zelle der Sammlungsansicht nicht glatt. Haben Sie eine Lösung dafür?
Sathish Kumar Gurunathan

6

contentView-Ankerrätsel:

In einem bizarren Fall dies

    contentView.translatesAutoresizingMaskIntoConstraints = false

würde nicht funktionieren. Der contentView wurden vier explizite Anker hinzugefügt, und es hat funktioniert.

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

und wie immer

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

im YourLayout: UICollectionViewFlowLayout

Wer weiß? Könnte jemandem helfen.

Anerkennung

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

stolperte dort über das Trinkgeld - habe es in all den 1000er-Artikeln dazu nirgendwo anders gesehen.


3

Ich habe eine dynamische Zellenhöhe der Sammlungsansicht erstellt. Hier ist Git Hub Repo .

Finden Sie heraus, warum PreferredLayoutAttributesFittingAttributes mehrmals aufgerufen wird. Eigentlich wird es mindestens 3 mal aufgerufen.

Das Konsolenprotokollbild: Geben Sie hier die Bildbeschreibung ein

1. bevorzugteLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Der layoutAttributes.frame.size.height ist aktuelle Stand 57.5 .

2. bevorzugteLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Die Zellenrahmenhöhe wurde auf 534,5 geändert erwartungsgemäß . Die Sammlungsansicht ist jedoch immer noch gleich hoch.

3. PreferredLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

Sie können sehen, dass die Höhe der Sammlungsansicht von 0 auf 477 geändert wurde .

Das Verhalten ist ähnlich wie beim Scrollen:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

Am Anfang dachte ich, diese Methode rufe nur einmal auf. Also habe ich wie folgt codiert:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

Diese Linie:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

führt zu einer Endlosschleife des Systemaufrufs und einem Absturz der App.

Bei jeder geänderten Größe werden die bevorzugten LayoutAttributesFittingAttributes aller Zellen immer wieder überprüft, bis sich die Positionen aller Zellen (dh Frames) nicht mehr ändern.


2

Zusätzlich zu den obigen Antworten

So stellen Sie sicher stellen Sie estimatedItemSize Eigenschaft UICollectionViewFlowLayout bis zu einem gewissen Größe und nicht implementieren sizeForItem: atIndexPath Delegatmethode.

Das ist es.


1

Wenn Sie die UICollectionViewDelegateFlowLayout-Methode implementieren:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

Wenn Sie anrufen collectionview performBatchUpdates:completion:, wird die Größenhöhe sizeForItemAtIndexPathanstelle von verwendet preferredLayoutAttributesFittingAttributes.

Der Renderprozess von performBatchUpdates:completionwird die Methode durchlaufen, preferredLayoutAttributesFittingAttributesaber Ihre Änderungen werden ignoriert.


Ich habe dieses Fehlverhalten gesehen, aber nur, wenn die geschätzte Größe Null ist. Sind Sie sicher, dass Sie eine geschätzte Größe festlegen?
Dgatwood

1

Wem auch immer es helfen mag,

Ich hatte diesen fiesen Absturz, wenn estimatedItemSizeeingestellt war. Auch wenn ich 0 in zurückgegeben habe numberOfItemsInSection. Daher waren die Zellen selbst und ihr automatisches Layout nicht die Ursache des Absturzes ... Die collectionView stürzte nur ab, auch wenn sie leer war, nur weilestimatedItemSize auf Selbstgröße eingestellt war.

In meinem Fall habe ich mein Projekt von einem Controller mit einer CollectionView zu einem CollectionViewController neu organisiert und es hat funktioniert.

Stelle dir das vor.


1
Versuchen Sie, collectionView.collectionViewLayout.invalidateLayout()danach anzurufen collectionView.reloadData().
Chengsam

Fügen Sie für ios10 und darunter diesen Code hinzu: `überschreiben Sie func viewWillLayoutSubviews () {super.viewWillLayoutSubviews (), wenn #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma

1

Für alle, die alles ohne Glück versucht haben, ist dies das einzige, was es für mich zum Laufen gebracht hat. Versuchen Sie für die mehrzeiligen Beschriftungen in der Zelle, diese magische Linie hinzuzufügen:

label.preferredMaxLayoutWidth = 200

Mehr Infos: hier

Prost!


0

Die obige Beispielmethode wird nicht kompiliert. Hier ist eine korrigierte Version (aber nicht getestet, ob es funktioniert oder nicht.)

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}

0

Weitere Informationen aktualisieren:

  • Wenn Sie verwenden flowLayout.estimatedItemSize, schlagen Sie vor , iOS8.3 spätere Version zu verwenden. Vor iOS8.3 stürzt es ab [super layoutAttributesForElementsInRect:rect];. Die Fehlermeldung lautet

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • Zweitens führt in der iOS8.x-Version dazu, flowLayout.estimatedItemSizedass andere Einstellungen für das Einfügen von Abschnitten nicht funktionierten. dh Funktion : (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.


0

Die Lösung umfasst 4 wichtige Schritte:

  1. Aktivieren der dynamischen Zellengröße

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Aktivieren Sie explizit das automatische Layout und heften Sie es contentViewan die Ränder der Zelle, da dies contentViewdie Selbstgröße verhindert, da das automatische Layout nicht aktiviert ist.
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. Legen Sie die Einschränkung contentView.widthAnchor.con von fest collectionView(:cellForItemAt:), um die Breite von contentView auf die Breite von collectionView zu beschränken.

Es passiert wie folgt; Wir setzen die maxWidthVariable der Zelle auf die Breite von CollectionView bei collectionView(:cellForItemAt:)und in maxWidthder didSetMethode von widthAnchor.constant auf maxWidth.

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

Da Sie die Selbstgröße von UITextView aktivieren möchten, müssen Sie einen zusätzlichen Schritt ausführen:

4. Berechnen Sie die heightAnchor.constant von UITextView und legen Sie sie fest.

Wenn also die Breite von contentView festgelegt wird, passen wir die Höhe von UITextView entlang in didSetan an maxWidth.

In UICollectionViewCell:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        contentViewWidthAnchor.constant = maxWidth
        contentViewWidthAnchor.isActive = true

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

Mit diesen Schritten erhalten Sie das gewünschte Ergebnis. Hier ist eine vollständige Übersicht

Vielen Dank an Vadim Bulavin für seinen klaren und aufschlussreichen Blog-Beitrag - Collection View Cells Self-Sizing: Schritt für Schritt Tutorial


-2

Ich habe versucht, zu verwenden, estimatedItemSizeaber es gab eine Reihe von Fehlern beim Einfügen und Löschen von Zellen, wenn die estimatedItemSizenicht genau der Höhe der Zelle entsprachen. Ich hörte auf zu setzen estimatedItemSizeund implementierte dynamische Zellen mithilfe eines Prototyps. So geht's:

Erstellen Sie dieses Protokoll:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

Implementieren Sie dieses Protokoll in Ihrer Gewohnheit UICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

Passen Sie nun Ihren Controller an UICollectionViewDelegateFlowLayoutdieses Feld an und geben Sie Folgendes ein:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

Es wird als Prototypzelle verwendet, um Daten zu binden und dann zu bestimmen, wie sich diese Daten auf die Dimension auswirken, für die Sie dynamisch sein möchten

Endlich, das UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:) muss das umgesetzt werden:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

und das ist es. Das Zurückgeben der Zellengröße collectionView(:layout:sizeForItemAt:)auf diese Weise, ohne dass ich sie verwenden muss estimatedItemSize, und das Einfügen und Löschen von Zellen funktioniert einwandfrei.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.