Cómo determinar la altura de UICollectionView con FlowLayout

118

Tengo un UICollectionViewcon un UICollectionViewFlowLayout, y quiero calcular el tamaño de su contenido (para devolverlo enintrinsicContentSize necesario ajustar su altura a través de AutoLayout).

El problema es: incluso si tengo una altura fija e igual para todas las celdas, no sé cuántas "filas" / líneas tengo en el UICollectionView. Tampoco puedo determinar ese recuento por la cantidad de elementos en mi fuente de datos, ya que las celdas que representan los elementos de datos varían en ancho, por lo que también lo hace la cantidad de elementos que tengo en una línea de UICollectionView.

Dado que no pude encontrar ninguna pista sobre este tema en la documentación oficial y buscar en Google no me trajo más, cualquier ayuda e idea sería muy apreciada.

Temblor de Ignatius
fuente

Respuestas:

255

¡Guau! Por alguna razón, después de horas de investigación, encontré una respuesta bastante fácil a mi pregunta: estaba buscando completamente en el lugar equivocado, buscando en toda la documentación que pude encontrar UICollectionView.

La solución simple y fácil radica en el diseño subyacente: simplemente llame collectionViewContentSizea su myCollectionView.collectionViewLayoutpropiedad y obtendrá la altura y el ancho del contenido como CGSize. Es tan fácil como eso.

Temblor de Ignatius
fuente
1
whoa, deseo que UITableView tenga algo similar
Chris Chen
1
Ignatus, @jbandi, Chris et al, ¿puedes ampliar esto? Cuando probé con una dirección de desplazamiento horizontal con un gran espaciado entre elementos (para que los elementos se dispusieran horizontalmente), la vista de colección devolvió un tamaño de contenido intrínseco no válido (-1, -1), y collectionViewContentSize devolvió lo que estaba efectivamente los límites de la vista de colección, sin tener en cuenta que solo había una fila rodeada de espacio. En resumen, no fue muy intrínseco. ¡Gracias!
Chris Conover
8
¿Cuál es la diferencia entre collectionViewContentSize y contentSize de la vista de desplazamiento?
Rounak
Cuando llama a contentSize de la vista de colección en viewDidLoad, devuelve el marco de la vista de colección, collectionViewContentSize devuelve el tamaño de contenido correcto.
Fury
1
Para quién puede ayudar: tuve que hacer un "layoutIfNeeded ()" antes de esto para poder hacerlo funcionar.
asanli
37

En caso de que esté utilizando el diseño automático , puede crear una subclase deUICollectionView

Si usa el siguiente código, entonces no tiene que especificar ninguna restricción de altura para la vista de colección, ya que variaría según el contenido de la vista de colección.

A continuación se muestra la implementación:

@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
usuario1046037
fuente
12
no funcionó para mí ... Estoy usando mi CollectionView dentro de un UITableViewCell y me gustaría que la vista de tabla crezca en altura a medida que la vista de colección crece en altura usando iOS8 UITableViewAutomaticDimension Pero mi vista de colección permanece pequeña :(
Georg
Increíble. ¡Solución corta y dulce para mi UICollectionView dentro de un UIScrollView!
dotToString
2
Una cosa importante es deshabilitar el desplazamiento y las barras de desplazamiento en la vista de colección
Georg
1
El mismo problema que Georg, tiene aproximadamente 50px de altura cuando debería ser más de 400
xtrinch
3
Sí, configuro la vista de colección en "no scrollabe, no scollbars" y luego la recargo y luego se establece el contenido de la vista de tabla. Además, la vista de colección es una subclase que devuelve su tamaño de contenido como intrinsicContentSize. ¡Espero que ayude!
Georg
25

En viewDidAppearpuedes conseguirlo por:

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

Tal vez cuando recargue los datos, luego necesite calcular una nueva altura con nuevos datos, entonces puede obtenerlo: agregue un observador para escuchar cuando haya CollectionViewterminado de recargar los datos en viewdidload:

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

Luego agregue la función de abajo para obtener una nueva altura o hacer cualquier cosa después de que la vista de colección haya terminado de recargar:

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

Y no olvide eliminar el observador:

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

user1046037 responder en Swift ...

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

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
Nick Wood
fuente
Funciona perfectamente, pero si tiene algo como el UILabelanterior, el contenido de collectionViewpasar por encima de otro elemento anterior, ¿tiene otra solución?
KolaCaine
11

Código Swift 3 para la respuesta del usuario 1046037

import UIKit

class DynamicCollectionView: UICollectionView {

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

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}
Mohammad Sadiq Shaikh
fuente
Soy más reciente, y no estoy seguro de dónde y cómo usar esto en mi ViewController. ¿Puede hacer una edición con la respuesta ...?
Abirami Bala
1
simplemente configure esta clase en su vista de colección en Storyboard
Mohammad Sadiq Shaikh
7

Rápido 4

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

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}
Wilson
fuente
1
Descubrí que no funciona en iPhone 6/7 plus y XR, XS-MAX
Rahul K Rajan
7

Rápido 4.2

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

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}
Cesare
fuente
4

Es demasiado tarde para responder a esta pregunta, pero recientemente pasé por este problema.
La respuesta anterior de @ lee me ayudó mucho a obtener mi respuesta. Pero esa respuesta se limita a objetivo-c y estaba trabajando en rápido .
@lee Con referencia a su respuesta, permítame permitir que el usuario rápido resuelva este problema fácilmente. Para eso, siga los pasos a continuación:

Declare una CGFloatvariable en la sección de declaración:

var height : CGFloat!

En viewDidAppearpuedes conseguirlo por:

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

Tal vez cuando sus reloaddatos necesiten calcular una nueva altura con nuevos datos, entonces puede obtenerlos: addObserverpara escuchar cuando haya CollectionViewterminado de recargar los datos en viewWillAppear:

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

Luego agregue la función de abajo para obtener una nueva altura o haga cualquier cosa después de collectionviewterminar la recarga:

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
    }

Y no olvide eliminar el observador:

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

Espero que esto le ayude a resolver su problema rápidamente.

Er. Vihar
fuente
@ShikhaSharma: Puede agregar el código removeObserver dentro del método "ViewDidDisappear", que eliminará al observador solo después de que la vista confirme que ha desaparecido. Verifique la respuesta actualizada.
Er. Vihar
Recibo este error: Un -observeValueForKeyPath: ofObject: change: context: message fue recibido pero no manejado.
Shikha Sharma
@ShikhaSharma: Lo siento, es mi error especificar el método. Intente poner el código removeobserver en viewWillDisappear.
Er. Vihar
1

Esta respuesta se basa en @Er. La respuesta de Vihar. Puede implementar fácilmente la solución utilizando RxSwift

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

Versión rápida de @ user1046037:

public class DynamicCollectionView: UICollectionView {

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

    override public var intrinsicContentSize: CGSize {
        let intrinsicContentSize: CGSize = contentSize
        return intrinsicContentSize
    }
}
Joshua Hart
fuente
0

Swift 4.2, Xcode 10.1, iOS 12.1:

Por alguna razón, collectionView.contentSize.heightaparecía más pequeño que la altura resuelta de mi vista de colección. Primero, estaba usando una restricción de diseño automático relativa a la mitad de la altura de la supervista. Para solucionar esto, cambié la restricción para que sea relativa al "área segura" de la vista.

Esto me permitió establecer la altura de la celda para llenar la vista de mi colección usando 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)
}

antes de

restricción de altura relativa a la supervista mal resultado del simulador

Después

restricción de altura relativa al área segura buen resultado del simulador

Andrew Kirna
fuente
0

asumiendo que su collectionView se denomina collectionView , puede tomar la altura de la siguiente manera.

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

Además, será mejor que llames reloadDataantes de obtener la altura:

theCollectionView.collectionViewLayout.collectionViewContentSize;
guozqzzu
fuente