UICollectionView, Zellen voller Breite, ermöglichen die automatische Auslagerung der dynamischen Höhe?

120

In einer Vertikalen UICollectionView,

Ist es möglich, Zellen mit voller Breite zu haben , aber die dynamische Höhe durch Autolayout zu steuern ?

Dies scheint mir die "wichtigste Frage in iOS ohne wirklich gute Antwort" zu sein.


Wichtig:

Beachten Sie, dass in 99% der Fälle einfach eine Tabellenansicht verwendet werden muss, um Zellen mit voller Breite + dynamische Höhe für das automatische Auslegen zu erzielen . So einfach ist das.


Was ist ein Beispiel dafür, wo Sie eine Sammlungsansicht benötigen?

Sammlungsansichten sind unglaublich leistungsfähiger als Tabellenansichten.

Ein einfaches Beispiel , wo man tatsächlich hat eine Sammlung Ansicht verwenden, um volle Breite Zellen zu erreichen + automatisches Layout dynamische Höhe:

Wenn Sie animieren zwischen zwei Layouts in einer Sammlungsansicht . Zum Beispiel zwischen einem 1- und 2-Spalten-Layout, wenn sich das Gerät dreht.

Das ist eine gängige und normale Redewendung in iOS. Leider kann dies nur durch die Lösung des in dieser Qualitätssicherung aufgeworfenen Problems erreicht werden. : - /

Fattie
quelle
Ja ist möglich mit UICollectionViewDelegateFlowLayout Klasse
Sunil Prajapati
Würde dies nicht bedeuten, dass Sie tatsächlich eine tableView verwenden könnten? Welche zusätzlichen Funktionen wären für Sie erforderlich, um dies zu komplizieren?
Catalina T.
1
Hallo @CatalinaT. Sammlungsansichten bieten viele, viele, viele zusätzliche Funktionen gegenüber Tabellenansichten. Ein einfaches Beispiel ist das Hinzufügen von Physik. (Wenn Sie nicht wissen, was ich meine, ist es, wie Sie die "Hüpfburgen" -Listen in iOS
erstellen
Sie können auch irgendwie Physik Nehmen (wie SpringDampingund initialSpringVelocity) in Tabellenzelle ..... haben einen Blick auf diese Bouncy Liste mit Tableview ,,,, pastebin.com/pFRQhkrX Sie können die Animation TableViewCellin der Tabelle in willDisplayCell:(UITableViewCell *)celldelgate Methode @Fattie Sie annehmen können die Antwort, die Ihr Problem gelöst hat, um anderen zu helfen, die nach dem gleichen Problem suchen
Dhiru
ohk, hast du die Bouncy-Liste mit der Listenansicht ausprobiert? @ Fattie
Dhiru

Antworten:

122

1. Lösung für iOS 13+

Mit Swift 5.1 und iOS 13 können Sie Compositional Layout-Objekte verwenden verwenden, um Ihr Problem zu lösen.

Der folgende vollständige Beispielcode zeigt, wie mehrzeilig UILabelin voller Breite angezeigt wird UICollectionViewCell:

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        let size = NSCollectionLayoutSize(
            widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
            heightDimension: NSCollectionLayoutDimension.estimated(44)
        )
        let item = NSCollectionLayoutItem(layoutSize: size)
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
        section.interGroupSpacing = 10

        let headerFooterSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .absolute(40)
        )
        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: headerFooterSize,
            elementKind: "SectionHeaderElementKind",
            alignment: .top
        )
        section.boundarySupplementaryItems = [sectionHeader]

        let layout = UICollectionViewCompositionalLayout(section: section)
        collectionView.collectionViewLayout = layout
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { context in
            self.collectionView.collectionViewLayout.invalidateLayout()
        }, completion: nil)
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Erwartete Anzeige:

Geben Sie hier die Bildbeschreibung ein


2. Lösung für iOS 11+

Mit Swift 5.1 und iOS 11 können Sie eine Unterklasse UICollectionViewFlowLayoutfestlegen und deren estimatedItemSizeEigenschaft auf festlegen UICollectionViewFlowLayout.automaticSize(dies teilt dem System mit, dass Sie sich mit der automatischen Größenänderung befassen möchten UICollectionViewCell). Sie müssen dann überschreiben layoutAttributesForElements(in:)und die Zellenbreite layoutAttributesForItem(at:)einstellen. Zuletzt müssen Sie die preferredLayoutAttributesFitting(_:)Methode Ihrer Zelle überschreiben und ihre Höhe berechnen.

Der folgende vollständige Code zeigt, wie mehrzeilig UILabelin voller Breite angezeigt wird UIcollectionViewCell(eingeschränkt durch UICollectionViewden sicheren Bereich undUICollectionViewFlowLayout die Einfügungen):

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]
    let customFlowLayout = CustomFlowLayout()

    override func viewDidLoad() {
        super.viewDidLoad()

        customFlowLayout.sectionInsetReference = .fromContentInset // .fromContentInset is default
        customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        customFlowLayout.minimumInteritemSpacing = 10
        customFlowLayout.minimumLineSpacing = 10
        customFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        customFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)

        collectionView.collectionViewLayout = customFlowLayout
        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

}

CustomFlowLayout.swift

import UIKit

final class CustomFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
        layoutAttributesObjects?.forEach({ layoutAttributes in
            if layoutAttributes.representedElementCategory == .cell {
                if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        })
        return layoutAttributesObjects
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let collectionView = collectionView else {
            fatalError()
        }
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }

        layoutAttributes.frame.origin.x = sectionInset.left
        layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
        return layoutAttributes
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
        layoutIfNeeded()
        layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
        return layoutAttributes
    }

}

Hier sind einige alternative Implementierungen für preferredLayoutAttributesFitting(_:):

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    return layoutAttributes
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    label.preferredMaxLayoutWidth = layoutAttributes.frame.width
    layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
    return layoutAttributes
}

Erwartete Anzeige:

Geben Sie hier die Bildbeschreibung ein

Imanou Petit
quelle
Dies funktionierte gut für mich, bis ich Abschnittsüberschriften hinzufügte. Abschnittsüberschriften sind falsch platziert und decken andere Zellen in iOS 11 ab. Funktioniert jedoch in iOS 12 einwandfrei. Haben Sie Probleme beim Hinzufügen von Abschnittsüberschriften?
Nakul
Ich danke dir sehr. Du hast meine Woche gerettet. 🙇‍♂️
Gaetan
1
CollectionView scrollt nach oben, wenn sich die Größe ändert
Sajith
1
fantastische Neuigkeiten, @ImanouPetit! Ich wünschte, das Kopfgeld wäre größer! Fantastische Informationen zu Compositional Layout-Objekten, danke.
Fattie
1
Hallo, ich versuche das. Obwohl ich das Layout in viewdidload wie oben festgelegt habe, wird der Absturz angezeigt, der besagt, dass die Sammlungsansicht mit einem Layoutparameter ungleich Null initialisiert werden muss. Fehlt mir etwas? Dies ist Xcode 11 für iOS 13.
Geistmate
29

Problem

Sie suchen nach automatischer Höhe und möchten auch volle Breite haben, es ist nicht möglich, beide in Gebrauch zu bekommen UICollectionViewFlowLayoutAutomaticSize.

Wenn Sie dies tun möchten, UICollectionViewist unten die Lösung für Sie.

Lösung

Schritt I : Berechnen Sie die erwartete Höhe der Zelle

1. Wenn Sie nur UILabel in CollectionViewCellals die eingestellte numberOfLines=0und berechnet die erwartete Höhe UIlable, die alle drei paramters passieren

func heightForLable(text:String, font:UIFont, width:CGFloat) -> CGFloat {
    // pass string, font, LableWidth  
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
     label.numberOfLines = 0
     label.lineBreakMode = NSLineBreakMode.byWordWrapping
     label.font = font
     label.text = text
     label.sizeToFit()

     return label.frame.height
}

2. Wenn Ihr CollectionViewCellnur enthält UIImageViewund wenn es in der Höhe dynamisch sein soll, dann müssen Sie die Höhe von erhalten UIImage (Sie UIImageViewmüssen AspectRatioEinschränkungen haben)

// this will give you the height of your Image
let heightInPoints = image.size.height
let heightInPixels = heightInPoints * image.scale

3. Wenn es beide enthält, berechnen Sie ihre Höhe und addieren Sie sie.

SCHRITT II : Geben Sie die Größe von zurückCollectionViewCell

1. Fügen Sie UICollectionViewDelegateFlowLayoutIhrem viewController einen Delegaten hinzu

2. Implementieren Sie die Delegate-Methode

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

    // This is just for example, for the scenario Step-I -> 1 
    let yourWidthOfLable=self.view.size.width
    let font = UIFont(name: "Helvetica", size: 20.0)

    var expectedHeight = heightForLable(array[indePath.row], font: font, width:yourWidthOfLable)


    return CGSize(width: view.frame.width, height: expectedHeight)
}

Ich hoffe das hilft dir weiter.

Dhiru
quelle
1
Ist es möglich, eine UICollectionView mit horizontalem Bildlauf zu erstellen, bei der die Zellenhöhe mit dem automatischen Layout vertikal zunimmt?
Nr. 5
Ich möchte die gleiche, aber halbe Breite und dynamische Höhe erreichen
Mihir Mehta
return CGSize(width: view.frame.width/2, height: expectedHeight)Behalten Sie auch die Auffüllung im Auge, als die Breite zurückzugeben, da sonst zwei Zellen nicht in eine einzelne Zeile passen würden.
Dhiru
@ nr5 Hast du die Lösung bekommen? Meine Anforderung ist dieselbe wie Ihre. Vielen Dank.
Maulik Bhuptani
@ MaulikBhuptani Ich konnte es nicht vollständig mit Auto Layout machen. Musste eine benutzerdefinierte Layout-Klasse erstellen und einige Klassenmethoden überschreiben.
Nr. 5
18

Es gibt verschiedene Möglichkeiten, dieses Problem anzugehen.

Eine Möglichkeit besteht darin, dem Ablauflayout der Sammlungsansicht eine geschätzte Größe zuzuweisen und die Zellengröße zu berechnen.

Hinweis: Wie in den Kommentaren unten erwähnt, müssen Sie ab iOS 10 keine geschätzte Größe mehr angeben, um den Aufruf einer Zelle auszulösen func preferredLayoutAttributesFitting(_ layoutAttributes:). Zuvor (iOS 9) mussten Sie eine geschätzte Größe angeben, wenn Sie eine Zelle abfragen möchten. PrefferedLayoutAttributes.

(Angenommen, Sie verwenden Storyboards und die Sammlungsansicht ist über IB verbunden.)

override func viewDidLoad() {
    super.viewDidLoad()
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.estimatedItemSize = CGSize(width: 375, height: 200) // your average cell size
}

Für einfache Zellen reicht das normalerweise aus. Wenn die Größe immer noch falsch ist, können Sie sie in der Zelle der Sammlungsansicht überschreiben func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes, wodurch Sie die Zellengröße genauer steuern können. Hinweis: Sie müssen dem Flusslayout weiterhin eine geschätzte Größe geben .

Überschreiben Sie dann func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes, um die richtige Größe zurückzugeben.

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
    let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize)
    autoLayoutAttributes.frame = autoLayoutFrame
    return autoLayoutAttributes
}

Alternativ können Sie stattdessen eine Größenzelle verwenden, um die Größe der Zelle in der zu berechnen UICollectionViewDelegateFlowLayout.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.width
    let size = CGSize(width: width, height: 0)
    // assuming your collection view cell is a nib
    // you may also instantiate a instance of our cell from a storyboard
    let sizingCell = UINib(nibName: "yourNibName", bundle: nil).instantiate(withOwner: nil, options: nil).first as! YourCollectionViewCell
    sizingCell.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    sizingCell.frame.size = size
    sizingCell.configure(with: object[indexPath.row]) // what ever method configures your cell
    return sizingCell.contentView.systemLayoutSizeFitting(size, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
}

Dies sind zwar keine perfekten produktionsfertigen Beispiele, aber sie sollten Ihnen den richtigen Weg weisen. Ich kann nicht sagen, dass dies die beste Vorgehensweise ist, aber dies funktioniert für mich selbst bei ziemlich komplexen Zellen mit mehreren Beschriftungen, die möglicherweise in mehrere Zeilen eingeschlossen sind oder nicht.

Eric Murphey
quelle
@Fattie Unklar, wie 'esitmatedItemSize' für eine dynamisch dimensionierte Sammlungsansichtszelle irrelevant ist, daher würde ich gerne Ihre Gedanken hören. (kein Sarkasmus)
Eric Murphey
Wenn in dieser Antwort etwas Bestimmtes fehlt, das hilfreich ist und / oder was Sie als kanonisch betrachten, lassen Sie es mich wissen. Ich werde nicht über das Kopfgeld streiten, ich möchte nur jedem anderen helfen, der diese Frage sieht.
Eric Murphey
1
(Das Einschließen oder Nichteinschließen von EstimatedItemSize macht im aktuellen iOS keinen Unterschied und ist in beiden Fällen in der Frage "volle Breite + dynamische Höhe" überhaupt keine Hilfe.) Der Code am Ende, Schreibfedern werden heutzutage selten verwendet und hat es auch geringe Relevanz für eine dynamische Höhe (z. B. aus einigen Textansichten mit dynamischer Höhe). danke aber
Fattie
Ich bin auch zu dieser Lösung gekommen, aber in meinem Fall, iOS 11, Xcode 9.2, schien der Zelleninhalt von der Zelle selbst getrennt zu sein - ich konnte kein Bild erhalten, das auf die volle Höhe oder Breite der festen Dimension erweitert werden konnte. Ich bemerkte weiter, dass CV Einstellungen Einschränkungen in war preferredLayoutAttributesFitting. Ich habe dann in dieser Funktion eine eigene Einschränkung hinzugefügt, die an die feste Dimension von EstimatedItemSize, FWIW, gebunden ist (siehe meine Antwort unten).
Chris Conover
16

Ich habe eine ziemlich einfache Lösung für dieses Problem gefunden: In meiner CollectionViewCell habe ich ein UIView (), das eigentlich nur ein Hintergrund ist. Um die volle Breite zu erhalten, habe ich einfach die folgenden Anker gesetzt

bgView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width - 30).isActive = true // 30 is my added up left and right Inset
bgView.topAnchor.constraint(equalTo: topAnchor).isActive = true
bgView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
bgView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
bgView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

Die "Magie" geschieht in der ersten Zeile. Ich setze den widthAnchor dynamisch auf die Breite des Bildschirms. Wichtig ist auch, die Einfügungen Ihrer CollectionView zu subtrahieren. Andernfalls wird die Zelle nicht angezeigt. Wenn Sie keine solche Hintergrundansicht haben möchten, machen Sie sie einfach unsichtbar.

Das FlowLayout verwendet die folgenden Einstellungen

layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

Das Ergebnis ist eine Zelle voller Größe mit dynamischer Höhe.

Geben Sie hier die Bildbeschreibung ein

inf1783
quelle
1
Heilige Kuh - das ist unglaublich! so offensichtlich ! brillant !
Fattie
1
nur TBC @ inf1783, machst du das in UICollectionView? (keine Tabellenansicht) - richtig?
Fattie
1
@ Fattie Ja, es ist eine UICollectionView;)
inf1783
fantastisch @ inf1783, kann es kaum erwarten, dies auszuprobieren. Ein weiterer guter Weg, dies zu tun, besteht übrigens darin, die UIView zu unterklassifizieren und einfach eine intrinsische Breite hinzuzufügen (ich vermute, es würde den gleichen Effekt haben und es würde wahrscheinlich auch zur Storyboard-Zeit funktionieren)
Fattie
1
Jedes Mal, wenn ich dies versuche, lande ich in einer Endlosschleife mit Das Verhalten des UICollectionViewFlowLayout ist nicht definiert Fehler: /
Skodik.o
7

ARBEITEN!!! Getestet unter IOS: 12.1 Swift 4.1

Ich habe eine sehr einfache Lösung, die einfach ohne Einschränkungsbruch funktioniert.

Geben Sie hier die Bildbeschreibung ein

Meine ViewControllerClass

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    let cellId = "CustomCell"

    var source = ["nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,"]

    override func viewDidLoad() {
        super.viewDidLoad()

        self.collectionView.delegate = self
        self.collectionView.dataSource = self
        self.collectionView.register(UINib.init(nibName: cellId, bundle: nil), forCellWithReuseIdentifier: cellId)

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

    }

}


extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.source.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? CustomCell else { return UICollectionViewCell() }
        cell.setData(data: source[indexPath.item])
        return cell
    }


}

CustomCell-Klasse:

class CustomCell: UICollectionViewCell {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var widthConstraint: NSLayoutConstraint!

    override func awakeFromNib() {
        super.awakeFromNib()
        self.widthConstraint.constant = UIScreen.main.bounds.width
    }

    func setData(data: String) {
        self.label.text = data
    }

    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        return contentView.systemLayoutSizeFitting(CGSize(width: self.bounds.size.width, height: 1))
    }

}

Hauptbestandteil ist die systemLayoutSizeFitting Funktion in Customcell. Außerdem müssen wir die Breite der Ansicht in Cell mit Einschränkungen festlegen.

Abhishek Biswas
quelle
6

Sie müssen CollectionViewCell eine Breitenbeschränkung hinzufügen

class SelfSizingCell: UICollectionViewCell {

  override func awakeFromNib() {
      super.awakeFromNib()
      contentView.translatesAutoresizingMaskIntoConstraints = false
      contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
  }
}
Mecid
quelle
Endlich hat es jemand in zwei Zeilen perfekt gemacht
Omar N Shamali
Dadurch wird die Zelle auf eine feste Breite begrenzt, die sich kaum selbst dimensioniert. Wenn Sie das Gerät drehen oder die Bildschirmgröße auf einem iPad anpassen, bleibt es fest.
Thomas Verbeek
4
  1. Satz estimatedItemSizeIhres Ablauflayouts:

    collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
  2. Definieren Sie eine Breitenbeschränkung in der Zelle und stellen Sie sie so ein, dass sie der Breite der Übersicht entspricht:

    class CollectionViewCell: UICollectionViewCell {
        private var widthConstraint: NSLayoutConstraint?
    
        ...
    
        override init(frame: CGRect) {
            ...
            // Create width constraint to set it later.
            widthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0)
        }
    
        override func updateConstraints() {
            // Set width constraint to superview's width.
            widthConstraint?.constant = superview?.bounds.width ?? 0
            widthConstraint?.isActive = true
            super.updateConstraints()
        }
    
        ...
    }

Vollständiges Beispiel

Getestet auf iOS 11.

römisch
quelle
3

Persönlich habe ich die besten Möglichkeiten für eine UICollectionView gefunden, bei der AutoLayout die Größe bestimmt, während jede Zelle eine andere Größe haben kann, indem die Funktion UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath implementiert wird, während eine tatsächliche Zelle zum Messen der Größe verwendet wird.

Ich habe darüber in einem meiner Blog-Beiträge gesprochen

Hoffentlich hilft Ihnen dieser, das zu erreichen, was Sie wollen. Ich bin nicht 100% sicher, aber ich glaube, im Gegensatz zu UITableView, wo Sie tatsächlich eine vollautomatische Höhe von Zellen haben können, indem Sie AutoLayout inkonjunktion mit verwenden

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionView bietet AutoLayout nicht die Möglichkeit, die Größe zu bestimmen, da UICollectionViewCell nicht unbedingt die gesamte Bildschirmbreite ausfüllt.

Aber hier ist eine Frage für Sie : Wenn Sie Zellen mit voller Bildschirmbreite benötigen, warum verwenden Sie UICollectionView überhaupt gegenüber einer guten alten UITableView, die mit Zellen mit automatischer Größenanpassung geliefert wird?

xxtesaxx
quelle
Halten Sie es, Aprit unten sagt, dass UICollectionViewFlowLayoutAutomaticSize jetzt im Flow-Layout in iOS10 verfügbar ist ...
Fattie
Ich habe das auch gerade gesehen. Anscheinend ist das jetzt in iOS 10 eine Sache. Ich habe gerade den entsprechenden WWDC-Vortrag dazu gesehen: developer.apple.com/videos/play/wwdc2016/219 Dadurch wird die Zelle jedoch vollständig automatisch an den Inhalt angepasst. Ich bin nicht sicher, wie Sie die Zelle (mit AutoLayout) anweisen können, die Bildschirmbreite zu füllen, da Sie keine Einschränkung zwischen der UICollectionViewCell und ihrem übergeordneten Element (zumindest nicht in StoryBoards)
einrichten können
richtig. Wir scheinen einer tatsächlichen Lösung dieses unglaublich offensichtlichen Problems nicht näher zu sein. : /
Fattie
Laut dem Vortrag könnten Sie immer noch sizeThatFits () oder PreferredLayoutAttributesFitting () überschreiben und die Größe selbst berechnen, aber das ist nicht das, wonach das OP gefragt hat, denke ich. Wie auch immer, ich würde mich immer noch für den Anwendungsfall interessieren, wenn er / sie eine UICollectionViewCell in voller Breite benötigt und warum es so wichtig wäre, in diesem speziellen Fall keine UITableView zu verwenden (sicher, dass es bestimmte Fälle geben kann, aber dann denke ich Sie müssen nur einige Berechnungen selbst durchführen)
xxtesaxx
Wenn Sie darüber nachdenken, ist es ziemlich unglaublich, dass Sie nicht einfach "Spalten == 1" (und dann möglicherweise Spalten == 2, wenn sich das Gerät seitwärts befindet) mit Sammlungsansichten festlegen können.
Fattie
3

Gemäß meinem Kommentar zu Erics Antwort ist meine Lösung seiner sehr ähnlich, aber ich musste eine Einschränkung in PreferredSizeFor ... hinzufügen, um mich auf die feste Dimension zu beschränken.

    override func systemLayoutSizeFitting(
        _ targetSize: CGSize, withHorizontalFittingPriority
        horizontalFittingPriority: UILayoutPriority,
        verticalFittingPriority: UILayoutPriority) -> CGSize {

        width.constant = targetSize.width

        let size = contentView.systemLayoutSizeFitting(
            CGSize(width: targetSize.width, height: 1),
            withHorizontalFittingPriority: .required,
            verticalFittingPriority: verticalFittingPriority)

        print("\(#function) \(#line) \(targetSize) -> \(size)")
        return size
    }

Diese Frage enthält eine Reihe von Duplikaten. Ich habe sie hier ausführlich beantwortet und hier eine funktionierende Beispiel-App bereitgestellt .

Chris Conover
quelle
2

Ich bin mir nicht sicher, ob dies eine "wirklich gute Antwort" ist, aber ich verwende es, um dies zu erreichen. Mein Flusslayout ist horizontal und ich versuche, die Breite mit Autolayout anzupassen, sodass es Ihrer Situation ähnlich ist.

extension PhotoAlbumVC: UICollectionViewDelegateFlowLayout {
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // My height is static, but it could use the screen size if you wanted
    return CGSize(width: collectionView.frame.width - sectionInsets.left - sectionInsets.right, height: 60) 
  }
}

Dann starte ich im View Controller, in dem die Autolayout-Einschränkung geändert wird, eine NSNotification.

NotificationCenter.default.post(name: NSNotification.Name("constraintMoved"), object: self, userInfo: nil)

In meiner UICollectionView-Unterklasse warte ich auf diese Benachrichtigung:

// viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(handleConstraintNotification(notification:)), name: NSNotification.Name("constraintMoved"), object: nil)

und das Layout ungültig machen:

func handleConstraintNotification(notification: Notification) {
    self.collectionView?.collectionViewLayout.invalidateLayout()
}

Dies führt sizeForItemAtdazu, dass die neue Größe der Sammlungsansicht erneut aufgerufen wird. In Ihrem Fall sollte es angesichts der neuen Einschränkungen im Layout aktualisiert werden können.

Mark Suman
quelle
nur TBC Mark meinst du , dass du tatsächlich ändert die Größe einer Zelle? (dh die Zelle erscheint und es ist Größe X, dann passiert etwas in Ihrer App und es wird Größe Y ..?) thx
Fattie
Ja. Wenn der Benutzer ein Element auf den Bildschirm zieht, wird ein Ereignis ausgelöst, das die Größe der Zelle aktualisiert.
Mark Suman
1

Stellen Sie auf Ihrem viewDidLayoutSubviewsdie estimatedItemSizevolle Breite ein (Layout bezieht sich auf das UICollectionViewFlowLayout-Objekt):

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.bounds.size.width, height: 120)
}

Stellen Sie in Ihrer Zelle sicher, dass Ihre Einschränkungen sowohl den oberen als auch den unteren Rand der Zelle berühren (der folgende Code verwendet Kartografie, um das Festlegen der Einschränkungen zu vereinfachen, Sie können dies jedoch mit NSLayoutConstraint oder IB tun, wenn Sie möchten):

constrain(self, nameLabel, valueLabel) { view, name, value in
        name.top == view.top + 10
        name.left == view.left
        name.bottom == view.bottom - 10
        value.right == view.right
        value.centerY == view.centerY
    }

Voila, deine Zellen wachsen jetzt automatisch in der Höhe!

Raphael
quelle
0

Keine der Lösungen funktionierte für mich, da ich eine dynamische Breite benötige, um mich zwischen der Breite des iPhones anzupassen.

    class CustomLayoutFlow: UICollectionViewFlowLayout {
        override init() {
            super.init()
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        override var itemSize: CGSize {
            set { }
            get {
                let width = (self.collectionView?.frame.width)!
                let height = (self.collectionView?.frame.height)!
                return CGSize(width: width, height: height)
            }
        }
    }

    class TextCollectionViewCell: UICollectionViewCell {
        @IBOutlet weak var textView: UITextView!

        override func prepareForReuse() {
            super.prepareForReuse()
        }
    }




    class IntroViewController: UIViewController, UITextViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UINavigationControllerDelegate {
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionView: UICollectionView!
        var collectionViewLayout: CustomLayoutFlow!

        override func viewDidLoad() {
            super.viewDidLoad()

            self.collectionViewLayout = CustomLayoutFlow()
            self.collectionView.collectionViewLayout = self.collectionViewLayout
        }

        override func viewWillLayoutSubviews() {
            self.collectionViewTopDistanceConstraint.constant = UIScreen.main.bounds.height > 736 ? 94 : 70

            self.view.layoutIfNeeded()
        }
    }
iOS Flow
quelle
0

Ab iOS 10 haben wir dafür eine neue API für das Flow-Layout.

Alles was Sie tun müssen, ist Ihre flowLayout.estimatedItemSizeauf eine neue Konstante zu setzen UICollectionViewFlowLayoutAutomaticSize.

Quelle

Arpit Dongre
quelle
Hmm, bist du dir ziemlich sicher, dass es * in voller Breite ist ? Also im Wesentlichen eine Spalte?
Fattie
Dies kann der Fall sein, wenn Sie die Breite von UICollectionViewCell explizit auf die volle Breite festlegen.
Arpit Dongre
Nein, die Breite wird nicht auf die volle Höhe festgelegt und der geschätzte Größenwert wird ignoriert, es sei denn, Sie folgen Erics Antwort oben oder meiner unten / später.
Chris Conover
OK, dann hat das leider absolut keinen Zusammenhang mit dem Problem! heh! : O
Fattie
0

AutoLayout kann zum automatischen Ändern der Größe von Zellen in CollectionView in zwei einfachen Schritten verwendet werden:

  1. Aktivieren der dynamischen Zellengröße

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. collectionView(:cellForItemAt:)Erstellen Sie eine Containeransicht und legen Sie die Einschränkung containerView.widthAnchor.con von fest , um die Breite von contentView auf die Breite von collectionView zu beschränken.
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
            }
            containerViewWidthAnchor.constant = maxWidth
            containerViewWidthAnchor.isActive = true
        }
    }

    ....
}

Das ist es, was Sie das gewünschte Ergebnis erhalten. Den vollständigen Code finden Sie in den folgenden Abschnitten:

Referenz / Credits:

Bildschirmfoto: Geben Sie hier die Bildbeschreibung ein

JolasJoe
quelle
-6

Sie müssen die Klasse UICollectionViewDelegateFlowLayout auf Ihrem collectionViewController erben. Fügen Sie dann die Funktion hinzu:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width, height: 100)
}

Damit haben Sie die Breite Größe der Bildschirmbreite.

Und Sie haben jetzt einen collectionViewController mit Zeilen als tableViewController.

Wenn die Größe der Höhe jeder Zelle dynamisch sein soll, sollten Sie möglicherweise benutzerdefinierte Zellen für jede benötigte Zelle erstellen.

Edu
quelle
7
Dies ist genau nicht die Antwort auf die Frage :) Das würde eine feste Höhe von 100 ergeben, dh es ist so, wie man es programmiert hat, bevor es überhaupt ein Autolayout gab.
Fattie