UICollectionView Celle con dimensionamento automatico con layout automatico


207

Sto cercando di far UICollectionViewCellsfunzionare il ridimensionamento automatico con Auto Layout, ma non riesco a far dimensionare le celle in base al contenuto. Ho difficoltà a capire come le dimensioni della cella vengono aggiornate dal contenuto di ciò che è all'interno del contenuto della cella.

Ecco la configurazione che ho provato:

  • Personalizzato UICollectionViewCellcon a UITextViewnel suo contenuto Visualizza.
  • Lo scorrimento per UITextViewè disabilitato.
  • Il vincolo orizzontale di contentView è: "H: | [_textView (320)]", ovvero il punto UITextViewè bloccato a sinistra della cella con una larghezza esplicita di 320.
  • Il vincolo verticale di contentView è: "V: | -0 - [_ textView]", ovvero il punto UITextViewin cima alla cella.
  • Il UITextViewha un vincolo di altezza impostato su una costante che i UITextViewrapporti si inserisce il testo.

Ecco come appare con lo sfondo della cella impostato su rosso e lo UITextViewsfondo impostato su Blu: sfondo cella rosso, sfondo UITextView blu

Ho messo il progetto con cui ho giocato su GitHub qui .


Stai implementando il metodo sizeForItemAtIndexPath?
Daniel Galasko,

@DanielGalasko non lo sono. La mia comprensione è che la nuova funzionalità di celle di dimensionamento automatico in iOS 8 non richiede più di farlo.
rawbee,

non sono sicuro da dove hai preso quelle informazioni ma è ancora necessario, dai un'occhiata al brano del WWDC asciiwwdc.com/2014/sessions/226
Daniel Galasko,

ma di nuovo dipende dal tuo caso d'uso, assicurati di implementare
stimeItemSize

1
2019 - E 'fondamentale andare un'occhiata a questo: stackoverflow.com/a/58108897/294884
Fattie

Risposte:


309

Aggiornato per Swift 5

preferredLayoutAttributesFittingAttributesrinominato in preferredLayoutAttributesFittinge usa il dimensionamento automatico


Aggiornato per Swift 4

systemLayoutSizeFittingSize rinominato in systemLayoutSizeFitting


Aggiornato per iOS 9

Dopo aver visto la mia soluzione GitHub rompere sotto iOS 9, ho finalmente avuto il tempo di indagare a fondo sul problema. Ora ho aggiornato il repository per includere diversi esempi di diverse configurazioni per celle con dimensionamento automatico. La mia conclusione è che le cellule auto dimensionanti sono grandi in teoria ma disordinate in pratica. Un avvertimento quando si procede con le celle di dimensionamento automatico.

TL; DR

Dai un'occhiata al mio progetto GitHub


Le celle con ridimensionamento automatico sono supportate solo con il layout del flusso, quindi assicurati che sia quello che stai utilizzando.

Ci sono due cose che devi configurare per far funzionare le celle con ridimensionamento automatico.

1. Impostare estimatedItemSizesuUICollectionViewFlowLayout

Il layout del flusso diventerà di natura dinamica una volta impostata la estimatedItemSizeproprietà.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Aggiungi il supporto per il dimensionamento nella sottoclasse della tua cella

Questo è disponibile in 2 gusti; Layout automatico o sostituzione personalizzata di preferredLayoutAttributesFittingAttributes.

Crea e configura celle con Auto Layout

Non entrerò nei dettagli di questo in quanto esiste un brillante post SO sulla configurazione dei vincoli per una cella. Diffida solo che Xcode 6 abbia rotto un sacco di cose con iOS 7, quindi, se supporti iOS 7, dovrai fare cose come assicurarti che la maschera di ridimensionamento automatico sia impostata sul contenuto della cella e che i limiti del contenuto siano impostati come limiti della cella quando la cella è caricata (esawakeFromNib ).

Le cose di cui devi essere consapevole è che la tua cella deve essere più seriamente vincolata di una cella vista tabella. Ad esempio, se vuoi che la tua larghezza sia dinamica, la tua cella ha bisogno di un vincolo di altezza. Allo stesso modo, se si desidera che l'altezza sia dinamica, sarà necessario un vincolo di larghezza per la cella.

Implementa preferredLayoutAttributesFittingAttributesnella tua cella personalizzata

Quando questa funzione viene chiamata, la vista è già stata configurata con il contenuto (ovvero cellForItemè stata chiamata). Supponendo che i tuoi vincoli siano stati impostati in modo appropriato, potresti avere un'implementazione come questa:

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

NOTA Su iOS 9 il comportamento è leggermente cambiato e potrebbe causare arresti anomali della tua implementazione se non stai attento (vedi di più qui ). Quando si implementapreferredLayoutAttributesFittingAttributes è necessario assicurarsi di modificare il frame degli attributi di layout una sola volta. In caso contrario, il layout chiamerà l'implementazione indefinitamente e alla fine si arresterà in modo anomalo. Una soluzione è memorizzare nella cache la dimensione calcolata nella cella e invalidarla ogni volta che riutilizzi la cella o ne cambi il contenuto come ho fatto con ilisHeightCalculated proprietà.

Prova il tuo layout

A questo punto dovresti avere celle dinamiche "funzionanti" nella tua raccolta. Non ho ancora trovato la soluzione pronta all'uso sufficiente durante i miei test, quindi sentiti libero di commentare se hai. Sembra ancoraUITableView vincere la battaglia per il dimensionamento dinamico dell'IMHO.

Avvertenze

Ricorda che se stai utilizzando celle prototipo per calcolare la stimata Dimensione oggetto, ciò si interromperà se XIB utilizza classi di dimensioni . La ragione di ciò è che quando carichi la tua cella da una XIB verrà configurata la sua classe di dimensioni Undefined. Questo verrà interrotto solo su iOS 8 e versioni successive poiché su iOS 7 la classe di dimensioni verrà caricata in base al dispositivo (iPad = Regular-Any, iPhone = Compact-Any). È possibile impostare stimaItemSize senza caricare XIB oppure caricare la cella da XIB, aggiungerla a collectionView (questo imposterà traitCollection), eseguire il layout e rimuoverlo dalla superview. In alternativa, puoi anche fare in modo che la tua cella prevalga sul traitCollectiongetter e restituisca i tratti appropriati. Tocca a voi.

Fammi sapere se ho perso qualcosa, spero di aver aiutato e buona fortuna con la programmazione



2
Sto cercando di implementare la stessa cosa con il layout del flusso, tuttavia quando lo restituisco newFrame con dimensioni aggiornate, il layout non sembra ricalcolare le posizioni y, che sono ancora basate sull'altezza in stimatoSize. Cosa manca?
Anna

@anna stai chiamando invalidateLayout sul layout?
Daniel Galasko,

1
Ah, ho trovato la differenza, hai impostato l'altezza stimata piuttosto alta (400) mentre stavo facendo il minimo (44). Questo ha aiutato. Ma contentSize sembra ancora non essere impostato correttamente fino a quando non si scorre completamente, quindi non è molto utilizzabile. A proposito, ho cambiato UITextView in UILabel, stesso comportamento.
Anna

17
Questo sembra essere fondamentalmente rotto su iOS 9 GM. L'impostazione estimatedItemSizeporta a arresti anomali: sembra che ci sia un enorme bug nel modo in cui il layout automatico sta tentando di elaborare UICollectionViewCell.
Wes Campaigne,

3
Incontrando un problema simile, qualcuno ha trovato un problema?
Tony,

47

In iOS10 c'è una nuova costante chiamata UICollectionViewFlowLayout.automaticSize(precedentemente UICollectionViewFlowLayoutAutomaticSize), quindi invece:

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

puoi usare questo:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Ha prestazioni migliori soprattutto quando le celle nella vista della raccolta hanno una larghezza costante

Accesso al layout del flusso:

override func viewDidLoad() {
   super.viewDidLoad()

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

Swift 5 aggiornato:

override func viewDidLoad() {
   super.viewDidLoad()

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

8
Dove lo metti? in viewdidload? Come si fa a gestire il flowlayout?
UKDataGeek

1
Sono riuscito a implementarlo, ma ha uno strano comportamento che lo rende centrare le celle se esiste una singola cella. Ecco la domanda Stack Overflow su di esso - mi ha messo in difficoltà: stackoverflow.com/questions/43266924/...
UKDataGeek

1
È possibile accedere al layout del flusso tramite la raccolta Visualizzazioni collectionViewLayoute verificare che sia di tipoUICollectionViewFlowLayout
d4Rk

4
rende le mie celle molto piccole, inutili
user924

44

Alcune modifiche chiave alla risposta di Daniel Galasko hanno risolto tutti i miei problemi. Sfortunatamente, non ho abbastanza reputazione per commentare direttamente (ancora).

Nel passaggio 1, quando si utilizza il layout automatico, è sufficiente aggiungere un UIView genitore singolo alla cella. TUTTO all'interno della cella deve essere una sottoview del genitore. Ciò ha risposto a tutti i miei problemi. Mentre Xcode lo aggiunge automaticamente per UITableViewCells, non lo fa (ma dovrebbe) per UICollectionViewCells. Secondo i documenti :

Per configurare l'aspetto della tua cella, aggiungi le viste necessarie per presentare il contenuto dell'elemento dati come viste secondarie alla vista nella proprietà contentView. Non aggiungere direttamente visualizzazioni secondarie alla cella stessa.

Quindi saltare completamente il passaggio 3. Non è necessario.


1
Interessante; questo è specifico per Xibs, ma grazie, proverò a confondermi con il progetto Github e vedrò se posso riprodurlo. Forse dividere la risposta in Xib vs programmatico
Daniel Galasko,

Molto utile . Grazie!
ProblemaSlover

2
iOS 9, Xcode 7. Le celle sono prototipate nello storyboard, impostare una sottoclasse personalizzata. Ho provato a creare una proprietà chiamata contentView, Xcode si lamenta che è in conflitto con la proprietà esistente. Ho provato ad aggiungere subview self.contentViewe impostare i vincoli, quindi l'app si arresta in modo anomalo .
Nicolas Miari,

@NicolasMiari Non so come farlo a livello di codice, la mia soluzione è davvero solo quella di aggiungere una singola vista nel XIB dove tutto va al suo interno. Non è necessario creare una proprietà o altro.
Matt Koala,

Sto affrontando lo stesso problema di @NicolasMiari. Quando ho impostato la stima di ContentSize per le celle prototipate nello storyboard con vincoli di autolayout su iOS 9 / Xcode 7, l'app si arresta in modo anomalo con un'eccezione di accesso errata e nessuna utile stacktrace
wasabi

34

In iOS 10+ questo è un processo in 2 passaggi molto semplice.

  1. Assicurati che tutto il contenuto della tua cella sia inserito in un singolo UIView (o all'interno di un discendente di UIView come UIStackView che semplifica molto il trasferimento automatico). Proprio come con il ridimensionamento dinamico di UITableViewCells, l'intera gerarchia della vista deve avere i vincoli configurati, dal contenitore più esterno alla vista più interna. Ciò include i vincoli tra UICollectionViewCell e la vista figlio immediata

  2. Indica che il flusso di UICollectionView viene ridimensionato automaticamente

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Sono curioso, in che modo il wrapping dei contenuti delle celle in una vista UIV renderebbe migliore il layout automatico? Ho pensato che avrebbe funzionato meglio con una gerarchia più superficiale?
James Heald,

1
Non ho menzionato le prestazioni, solo la semplicità. Le celle con ridimensionamento automatico funzionano solo se si hanno vincoli che si estendono completamente tra sinistra e destra e tra la parte superiore e quella inferiore. Raggiungere ciò è più semplice quando tutte le viste sono racchiuse in un singolo contenitore. Se quel contenitore è un UIStackView è più facile che se sia un qualsiasi altro discendente di UIView.
Marmoy,

Potete per favore dirmi come impostare l'altezza costante (come impostare l'altezza 25 e la larghezza verrà automaticamente modificata) per UICollectionViewFlowLayoutAutomaticSize.
Svetoslav Atanasov,

Usando Swift 4.1, Xcode mi dice che UICollectionViewFlowLayout.automaticSizeè stato rinominato UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth,

14
  1. Aggiungi flowLayout su viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Inoltre, imposta un UIView come mainContainer per la tua cella e aggiungi tutte le viste richieste al suo interno.

  3. Fai riferimento a questo fantastico tutorial strabiliante per ulteriori riferimenti: UICollectionView con cella di ridimensionamento automatico che utilizza il layout automatico in iOS 9 e 10


11

MODIFICA 19/11/19: per iOS 13, basta usare UICollectionViewCompositionalLayout con altezze stimate. Non perdere tempo a occuparti di questa API rotta.

Dopo aver lottato con questo per qualche tempo, ho notato che il ridimensionamento non funziona per UITextViews se non disabiliti lo scorrimento:

let textView = UITextView()
textView.scrollEnabled = false

quando provo a scorrere, la cella della vista raccolta non è liscia. Hai qualche soluzione per questo?
Sathish Kumar Gurunathan,

6

contentView anchor mystery:

In un caso bizzarro questo

    contentView.translatesAutoresizingMaskIntoConstraints = false

non funzionerebbe. Aggiunti quattro ancoraggi espliciti a contentView e ha funzionato.

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

e come al solito

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

nel YourLayout: UICollectionViewFlowLayout

Chissà? Potrebbe aiutare qualcuno.

Credito

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

ci siamo imbattuti in punta lì - mai visto altrove in tutti gli articoli del 1000 su questo.


3

Ho fatto un'altezza dinamica della cella della vista raccolta. Ecco il repository git hub .

E, scava perché perché PreferLayoutAttributesFittingAttributes viene chiamato più di una volta. In realtà, verrà chiamato almeno 3 volte.

L'immagine del registro della console: inserisci qui la descrizione dell'immagine

1o preferitoLayoutAttributesFittingAttributes :

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

LayoutAttributes.frame.size.height è lo stato corrente 57.5 .

2 ° preferitoLayoutAttributesFittingAttributes :

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

L'altezza del telaio della cella è cambiata in 534,5 come previsto. Tuttavia, la vista della raccolta è ancora pari a zero.

3o preferitoLayoutAttributesFittingAttributes :

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

Puoi vedere che l' altezza della vista della raccolta è stata modificata da 0 a 477 .

Il comportamento è simile a gestire lo scorrimento:

1. Before self-sizing cell

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

3. Did changed self-sizing cell

All'inizio, ho pensato che questo metodo chiamasse solo una volta. Quindi ho codificato come segue:

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

Questa linea:

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

causerà il loop infinito della chiamata di sistema e l'arresto anomalo dell'app.

Qualsiasi dimensione modificata, convaliderà tutte le celleLayoutAttributesFittingAttributes di tutte le celle fino a quando le posizioni di ogni cella (ovvero i frame) non cambieranno più.


2

Oltre alle risposte di cui sopra,

Basta fare in modo di impostare estimatedItemSize proprietà di UICollectionViewFlowLayout ad alcune dimensioni e non implementare sizeForItem: atIndexPath metodo delegato.

Questo è tutto.


1

Se si implementa il metodo UICollectionViewDelegateFlowLayout:

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

Quando si chiama collectionview performBatchUpdates:completion:, verrà utilizzata l'altezza della dimensione sizeForItemAtIndexPathanziché preferredLayoutAttributesFittingAttributes .

Il processo di rendering di performBatchUpdates:completionpasserà attraverso il metodo preferredLayoutAttributesFittingAttributesma ignora le modifiche.


Ho visto questo comportamento scorretto, ma solo quando la dimensione stimata è zero. Sei sicuro di impostare una dimensione stimata?
dgatwood,

1

Per chiunque possa essere d'aiuto,

Ho avuto quel brutto incidente se è estimatedItemSizestato impostato. Anche se ho restituito 0 in numberOfItemsInSection. Pertanto, le celle stesse e il loro layout automatico non sono state la causa del crash ... collectionView ha subito un crash, anche se vuoto, solo perché estimatedItemSizeera impostato per il ridimensionamento automatico.

Nel mio caso ho riorganizzato il mio progetto, da un controller contenente un collectionView a un collectionViewController, e ha funzionato.

Vai a capire.


1
Prova a chiamare collectionView.collectionViewLayout.invalidateLayout()dopo collectionView.reloadData().
chengsam,

per iOS10 e seguenti aggiungere questo codice `override func viewWillLayoutSubviews () {super.viewWillLayoutSubviews () if #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma

1

Per chiunque abbia provato tutto senza fortuna, questa è l'unica cosa che ha funzionato per me. Per le etichette multilinea all'interno della cella, prova ad aggiungere questa linea magica:

label.preferredMaxLayoutWidth = 200

Maggiori informazioni: qui

Saluti!


0

Il metodo di esempio sopra non viene compilato. Ecco una versione corretta (ma non testata sul funzionamento o meno).

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

Aggiorna ulteriori informazioni:

  • Se usi flowLayout.estimatedItemSize, suggerisci di utilizzare iOS8.3 versione successiva. Prima di iOS8.3, andrà in crash [super layoutAttributesForElementsInRect:rect];. Il messaggio di errore è

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

  • In secondo luogo, nella versione iOS8.x, flowLayout.estimatedItemSizele diverse impostazioni dell'inserzione di sezione non funzionavano. per esempio una funzione: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.


0

La soluzione comprende 4 passaggi importanti:

  1. Abilitazione del dimensionamento dinamico delle celle

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Abilita esplicitamente il layout automatico e pin contentViewai bordi della cella perché contentViewimpedisce il dimensionamento automatico, dato che non ha il layout automatico abilitato.
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. Impostare contentView.widthAnchor.constraint da collectionView(:cellForItemAt:)per limitare la larghezza di contentView alla larghezza di collectionView.

Succede come segue; imposteremo la maxWidthvariabile di cella sulla larghezza di CollectionView al metodo di collectionView(:cellForItemAt:)e in maxWidthe didSetimposteremo widthAnchor.constant su 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
        }
    }

    ....
}

Poiché si desidera abilitare il ridimensionamento automatico di UITextView, è necessario eseguire un ulteriore passaggio;

4. Calcola e imposta l'altezzaAnchor.constant di UITextView.

Quindi, ogni volta che viene impostata la larghezza di contentView, regoleremo l'altezza di UITextView lungo didSetdi maxWidth.

All'interno di 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
    }
}

Questi passaggi ti daranno il risultato desiderato. Ecco un eseguibile completo Gist

Grazie a Vadim Bulavin per il suo post sul blog chiaro e illuminante - Collezione Vedi celle auto-dimensionamento: tutorial passo dopo passo


-2

Ho provato a usare estimatedItemSizema c'erano un sacco di bug durante l'inserimento e l'eliminazione di celle se estimatedItemSizenon era esattamente uguale all'altezza della cella. ho smesso di impostare estimatedItemSizee implementare celle dinamiche utilizzando una cella prototipo. ecco come è fatto:

creare questo protocollo:

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

implementare questo protocollo nella tua abitudine 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
    }
}

ora rendi il tuo controller conforme UICollectionViewDelegateFlowLayoute, in esso, dispone di questo campo:

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
}

verrà utilizzato come cella prototipo per associare i dati e quindi determinare in che modo tali dati hanno influenzato la dimensione che si desidera essere dinamica

infine, UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)deve essere implementato:

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

e basta. Restituire le dimensioni della cella collectionView(:layout:sizeForItemAt:)in questo modo impedendomi di dover usare estimatedItemSizee l'inserimento e l'eliminazione di celle funziona perfettamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.