Come determinare l'altezza di UICollectionView con FlowLayout


118

Ho un UICollectionViewcon UICollectionViewFlowLayoute voglio calcolare la dimensione del contenuto (per il ritorno intrinsicContentSizenecessario per regolare la sua altezza tramite AutoLayout).

Il problema è: anche se ho un'altezza fissa e uguale per tutte le celle, non so quante "righe" / righe ho nel file UICollectionView. Inoltre, non riesco a determinare il conteggio in base al numero di elementi nella mia origine dati, poiché le celle che rappresentano gli elementi di dati variano in larghezza, di conseguenza il numero di elementi che ho in una riga del file UICollectionView.

Dal momento che non sono riuscito a trovare alcun suggerimento su questo argomento nella documentazione ufficiale e il googling non mi ha portato oltre, qualsiasi aiuto e idea sarebbero stati molto apprezzati.

Risposte:


255

Whoa! Per qualche ragione, dopo ore di ricerca, ora ho trovato una risposta abbastanza semplice alla mia domanda: stavo cercando completamente nel posto sbagliato, scavando attraverso tutta la documentazione che potevo trovare UICollectionView.

La soluzione semplice e facile sta nel layout sottostante: basta chiamare la collectionViewContentSizetua myCollectionView.collectionViewLayoutproprietà e ottieni l'altezza e la larghezza del contenuto come CGSize. È così facile.


1
whoa, vorrei che UITableView avesse qualcosa di simile
Chris Chen

1
Ignatus, @jbandi, Chris et al, puoi approfondire questo argomento? Quando ho eseguito il test con una direzione di scorrimento orizzontale con una grande spaziatura tra gli elementi (in modo che gli elementi fossero disposti orizzontalmente), la vista della raccolta ha restituito una dimensione del contenuto intrinseco non valida (-1, -1) e collectionViewContentSize ha restituito ciò che era effettivamente i limiti della visualizzazione della raccolta, senza tenere conto del fatto che c'era solo una riga circondata dallo spazio. Insomma, non era molto intrinseco. Grazie!
Chris Conover

8
Qual è la differenza tra collectionViewContentSize e contentSize della visualizzazione a scorrimento?
rounak

Quando si chiama contentSize della visualizzazione della raccolta in viewDidLoad, restituisce il frame della visualizzazione della raccolta, collectionViewContentSize restituisce la dimensione del contenuto corretta.
Fury

1
Per chi può aiutare: ho dovuto fare un "layoutIfNeeded ()" prima di questo in modo da poterlo far funzionare.
asanli

37

Nel caso in cui utilizzi il layout automatico , puoi creare una sottoclasse diUICollectionView

Se si utilizza il codice riportato di seguito, non è necessario specificare alcun vincolo di altezza per la vista della raccolta poiché varierebbe in base al contenuto della vista della raccolta.

Di seguito è riportata l'implementazione:

@interface DynamicCollectionView : UICollectionView

@end

@implementation DynamicCollectionView

- (void) layoutSubviews
{
    [super layoutSubviews];

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

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

    return intrinsicContentSize;
}

@end

12
non ha funzionato per me ... Sto usando la mia CollectionView all'interno di un UITableViewCell e vorrei che la tableview crescesse in altezza man mano che la vista della raccolta cresce in altezza usando iOS8 UITableViewAutomaticDimension Ma la mia collectionview rimane piccola :(
Georg

Eccezionale. Soluzione breve e dolce al mio UICollectionView all'interno di un UIScrollView!
dotToString

2
Una cosa importante è disabilitare lo scorrimento e le barre di scorrimento nella vista della raccolta
Georg

1
Stesso problema di Georg, è alto circa 50px quando dovrebbe essere più di 400
xtrinch

3
Sì, ho impostato collectionview su "not scrollabe, no scollbars" e poi lo ricarico, quindi il contenuto della tableview è impostato. Inoltre, collectionview è una sottoclasse che restituisce la dimensione del contenuto come intrinsicContentSize. Spero che aiuti!
Georg

25

A viewDidAppearpuoi ottenerlo:

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

Forse quando ricarichi i dati devi calcolare una nuova altezza con nuovi dati, puoi ottenerla in questo modo: aggiungi l'osservatore per ascoltare quando hai CollectionViewfinito di ricaricare i dati in viewdidload:

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

Quindi aggiungi la funzione di seguito per ottenere una nuova altezza o fare qualsiasi cosa dopo che collectionview ha terminato il ricaricamento:

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

E non dimenticare di rimuovere l'osservatore:

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

11

user1046037 risposta in Swift ...

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

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

Funziona perfettamente ma se hai qualcosa di simile UILabelsopra, il contenuto collectionViewpassa sopra l'altro elemento sopra, hai un'altra soluzione?
KolaCaine

11

Codice Swift 3 per risposta user1046037

import UIKit

class DynamicCollectionView: UICollectionView {

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

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}

Sono un fresco e non sono sicuro di dove e come usarlo nel mio ViewController, puoi fare una modifica con la risposta .....?
Abirami Bala

1
imposta questa classe sulla tua raccoltaview in Storyboard
Mohammad Sadiq Shaikh

7

Swift 4

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

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

1
Ho scoperto che non funziona su iPhone 6/7 plus e XR, XS-MAX
Rahul K Rajan

7

Swift 4.2

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

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

4

È troppo tardi per rispondere a questa domanda, ma di recente ho affrontato questo problema.
La risposta di @lee sopra mi ha aiutato molto a ottenere la mia risposta. Ma quella risposta è limitata all'obiettivo-c e stavo lavorando in modo rapido .
@lee Con riferimento alla tua risposta, permettimi di consentire all'utente rapido di risolvere facilmente questo problema. Per questo, segui i passaggi seguenti:

Dichiarare una CGFloatvariabile nella sezione dichiarazione:

var height : CGFloat!

A viewDidAppearpuoi ottenerlo:

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

Forse quando i reloaddati devono quindi calcolare una nuova altezza con nuovi dati, puoi ottenerla: addObserverper ascoltare quando hai CollectionViewfinito di ricaricare i dati su viewWillAppear:

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

Quindi aggiungi la funzione di seguito per ottenere una nuova altezza o fare qualsiasi cosa al collectionviewtermine della ricarica:

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

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

        self.myCollectionView.frame = frame
    }

E non dimenticare di rimuovere l'osservatore:

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

Spero che questo ti aiuti a risolvere rapidamente il tuo problema.


@ShikhaSharma: puoi aggiungere il codice removeObserver all'interno del metodo "ViewDidDisappear", che rimuoverà l'osservatore solo dopo che la vista avrà confermato che è scomparso. Si prega di controllare la risposta aggiornata.
Ehm. Vihar

ricevo questo errore: un -observeValueForKeyPath: ofObject: change: context: il messaggio è stato ricevuto ma non gestito.
Shikha Sharma

@ShikhaSharma: Scusa, è un mio errore specificare il metodo. Prova a mettere il codice removeobserver in viewWillDisappear.
Ehm. Vihar

1

Questa risposta è basata su @Er. La risposta di Vihar. È possibile implementare facilmente la soluzione utilizzando RxSwift

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

0

Versione Swift di @ user1046037:

public class DynamicCollectionView: UICollectionView {

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

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

0

Swift 4.2, Xcode 10.1, iOS 12.1:

Per qualche ragione, collectionView.contentSize.heightappariva più piccolo dell'altezza risolta della vista della mia raccolta. Per prima cosa, stavo usando un vincolo di layout automatico relativo a 1/2 dell'altezza della superview.Per risolvere questo problema, ho modificato il vincolo in modo che fosse relativo all '"area sicura" della vista.

Questo mi ha permesso di impostare l'altezza della cella per riempire la vista della mia raccolta utilizzando collectionView.contentSize.height:

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

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

Prima

vincolo di altezza relativo a superview cattivo risultato del simulatore

Dopo

vincolo di altezza relativo all'area sicura buon risultato del simulatore


0

supponendo che la tua collectionView sia denominata collectionView puoi prendere l'altezza come segue.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height

0

Inoltre, faresti meglio a chiamare reloadDataprima di ottenere l'altezza:

theCollectionView.collectionViewLayout.collectionViewContentSize;
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.