UICollectionView Celdas de tamaño automático con diseño automático

207

Estoy tratando de auto dimensionarme UICollectionViewCells con Auto Layout, pero parece que no puedo hacer que las celdas se ajusten al contenido. Tengo problemas para comprender cómo se actualiza el tamaño de la celda a partir del contenido de lo que está dentro de contentView de la celda.

Aquí está la configuración que he probado:

  • Personalizado UICollectionViewCellcon un UITextViewen su contentView.
  • El desplazamiento para el UITextViewestá deshabilitado.
  • La restricción horizontal de contentView es: "H: | [_textView (320)]", es decir, UITextViewestá anclado a la izquierda de la celda con un ancho explícito de 320.
  • La restricción vertical de contentView es: "V: | -0 - [_ textView]", es decir, el UITextViewanclado en la parte superior de la celda.
  • El UITextViewtiene un conjunto altura limitación para una constante que laUITextView informes se encaje el texto.

Así es como se ve con el fondo de la celda en rojo, y el UITextView fondo establecido en Azul: cell background red, UITextView background blue

Puse el proyecto con el que he estado jugando en GitHub aquí .

rawbee
fuente
¿Estás implementando el método sizeForItemAtIndexPath?
Daniel Galasko
@DanielGalasko no lo soy. Tengo entendido que la nueva función de celdas de tamaño automático en iOS 8 ya no requiere que lo haga.
rawbee
No estoy seguro de dónde obtuviste esa información, pero aún debes hacerlo, echa un vistazo al extracto de WWDC asciiwwdc.com/2014/sessions/226
Daniel Galasko
pero nuevamente depende de su caso de uso, asegúrese de implementar el
tamaño
1
2019 - Es esencial ir a ver esto: stackoverflow.com/a/58108897/294884
Fattie

Respuestas:

309

Actualizado para Swift 5

preferredLayoutAttributesFittingAttributesrenombrado preferredLayoutAttributesFittingy usar dimensionamiento automático


Actualizado para Swift 4

systemLayoutSizeFittingSize renombrado a systemLayoutSizeFitting


Actualizado para iOS 9

Después de ver que mi solución GitHub se rompió en iOS 9, finalmente tuve el tiempo para investigar el problema por completo. Ahora he actualizado el repositorio para incluir varios ejemplos de diferentes configuraciones para celdas de tamaño propio. Mi conclusión es que las células auto dimensionantes son geniales en teoría pero desordenadas en la práctica. Una palabra de precaución al proceder con las células auto dimensionantes.

TL; DR

Mira mi proyecto GitHub


Las celdas de tamaño automático solo son compatibles con el diseño de flujo, así que asegúrese de que eso es lo que está utilizando.

Hay dos cosas que debe configurar para que las celdas de tamaño automático funcionen.

1. Establecer estimatedItemSizeenUICollectionViewFlowLayout

El diseño del flujo se volverá dinámico en la naturaleza una vez que configure la estimatedItemSizepropiedad.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Agregue soporte para dimensionar su subclase de celda

Esto viene en 2 sabores; Diseño automático o anulación personalizada de preferredLayoutAttributesFittingAttributes.

Crear y configurar celdas con diseño automático

No voy a entrar en detalles sobre esto ya que hay un brillante publicación SO sobre la configuración de restricciones para una celda. Solo tenga cuidado de que Xcode 6 rompió un montón de cosas con iOS 7, por lo que, si es compatible con iOS 7, deberá hacer cosas como asegurarse de que autoresizingMask esté configurado en contentView de la celda y que los límites de contentView estén establecidos como los límites de la celda cuando la celda está cargada (es decir awakeFromNib).

Lo que debe tener en cuenta es que su celda debe estar más seriamente restringida que una celda de vista de tabla. Por ejemplo, si desea que su ancho sea dinámico, su celda necesita una restricción de altura. Del mismo modo, si desea que la altura sea dinámica, necesitará una restricción de ancho para su celda.

Implementar preferredLayoutAttributesFittingAttributesen su celda personalizada

Cuando se llama a esta función, su vista ya se ha configurado con contenido (es decir, cellForItemse ha llamado). Suponiendo que sus restricciones se hayan establecido adecuadamente, podría tener una implementación como esta:

//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 En iOS 9, el comportamiento cambió un poco y podría causar fallas en su implementación si no tiene cuidado (vea más aquí ). Cuando implementaspreferredLayoutAttributesFittingAttributes , debe asegurarse de que solo cambie el marco de sus atributos de diseño una vez. Si no hace esto, el diseño llamará a su implementación indefinidamente y finalmente se bloqueará. Una solución es almacenar en caché el tamaño calculado en su celda e invalidarlo cada vez que reutilice la celda o cambie su contenido como lo he hecho con elisHeightCalculated propiedad.

Experimenta tu diseño

En este punto, debería tener celdas dinámicas "funcionales" en su collectionView. Todavía no he encontrado suficiente la solución lista para usar durante mis pruebas, así que siéntase libre de comentar. Todavía se siente comoUITableView gana la batalla por el tamaño dinámico en mi humilde opinión.

Advertencias

Tenga en cuenta que si está utilizando celdas prototipo para calcular el tamaño estimado del artículo, esto se romperá si su XIB usa clases de tamaño . La razón de esto es que cuando carga su celda desde un XIB, su clase de tamaño se configurará con Undefined. Esto solo se romperá en iOS 8 y superior, ya que en iOS 7 la clase de tamaño se cargará en función del dispositivo (iPad = Regular-Any, iPhone = Compact-Any). Puede establecer el tamaño estimado del artículo sin cargar el XIB, o puede cargar la celda desde el XIB, agregarlo a la colecciónView (esto establecerá la colección de caracteres), realizar el diseño y luego eliminarlo de la supervista. Alternativamente, también podría hacer que su celda anule el traitCollectiongetter y devuelva los rasgos apropiados. Tu decides.

Avíseme si me perdí algo, espero haber ayudado y buena suerte codificando


Daniel Galasko
fuente
2
Estoy tratando de implementar lo mismo con el diseño de flujo, sin embargo, cuando devuelvo newFrame con un tamaño actualizado, el diseño no parece recalcular las posiciones y, que todavía se basan en la altura en el tamaño estimado. Lo que falta
Anna
@anna, ¿estás llamando invalidateLayout en el diseño?
Daniel Galasko
1
Ah, encontré la diferencia, estableciste la altura estimada bastante alta (400) mientras que yo estaba haciendo el mínimo (44). Eso ayudó. Pero parece que contentSize todavía no se está configurando correctamente hasta que te desplazas hasta el final, por lo que aún no es muy útil. Por cierto, cambié UITextView a UILabel, el mismo comportamiento.
Anna
17
Esto parece estar fundamentalmente roto en iOS 9 GM. La configuración estimatedItemSizeconduce a bloqueos: parece haber un gran error en la forma en que el diseño automático intenta procesar la UICollectionViewCell.
Wes Campaigne
3
Al encontrarse con un problema similar, ¿alguien ha encontrado una solución?
Tony
47

En iOS10 hay una nueva constante llamada UICollectionViewFlowLayout.automaticSize(anteriormente UICollectionViewFlowLayoutAutomaticSize), así que en su lugar:

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

puedes usar esto:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Tiene un mejor rendimiento, especialmente cuando las celdas en su vista de colección tienen wid constante

Acceso al diseño de flujo:

override func viewDidLoad() {
   super.viewDidLoad()

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

Swift 5 actualizado:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}
pawel221
fuente
8
¿Dónde pones esto? en viewdidload? ¿Cómo se maneja el flowlayout?
UKDataGeek
1
Logré implementar esto, pero tiene un comportamiento extraño que hace que centre las celdas si hay una sola celda. Aquí está la pregunta de desbordamiento de pila al respecto: me ha
dejado
1
Puede acceder al diseño del flujo a través de collectionViews collectionViewLayouty simplemente verificar que sea de tipoUICollectionViewFlowLayout
d4Rk
44
hace que mis células sean muy pequeñas, inútiles
user924
44

Algunos cambios clave en la respuesta de Daniel Galasko solucionaron todos mis problemas. Desafortunadamente, no tengo suficiente reputación para comentar directamente (todavía).

En el paso 1, cuando use el diseño automático, simplemente agregue una vista UIView principal a la celda. TODO dentro de la celda debe ser una subvista del padre. Eso respondió a todos mis problemas. Si bien Xcode agrega esto para UITableViewCells automáticamente, no lo hace (pero debería) para UICollectionViewCells. Según los documentos :

Para configurar la apariencia de su celda, agregue las vistas necesarias para presentar el contenido del elemento de datos como subvistas a la vista en la propiedad contentView. No agregue directamente subvistas a la celda misma.

Luego omita el paso 3 por completo. No es necesario

Matt Koala
fuente
1
Interesante; esto es específicamente para Xibs pero, sin embargo, gracias, intentaré confundirme con el proyecto Github y ver si puedo reproducirlo. Tal vez dividir la respuesta en Xib vs programática
Daniel Galasko
Muy útil . ¡Gracias!
ProblemSlover
2
iOS 9, Xcode 7. Las celdas se prototipan en el guión gráfico, configuran una subclase personalizada. Intentó crear una propiedad llamada contentView, Xcode se queja de que entra en conflicto con la propiedad existente. Intenté agregar subvistas self.contentViewy establecer restricciones a eso, luego la aplicación se bloquea.
Nicolas Miari
@NicolasMiari No sé cómo hacer esto mediante programación, mi solución es realmente agregar una sola vista en el XIB donde todo va dentro de él. No necesita crear una propiedad ni nada.
Matt Koala
Estoy enfrentando el mismo problema que @NicolasMiari. Cuando configuré el tamaño de contenido estimado para las celdas prototipadas en el guión gráfico con restricciones de distribución automática en iOS 9 / Xcode 7, la aplicación se bloquea con una excepción de acceso incorrecto y sin un útil stacktrace
wasabi
34

En iOS 10+, este es un proceso muy simple de 2 pasos.

  1. Asegúrese de que todos los contenidos de sus celdas se coloquen dentro de una sola UIView (o dentro de un descendiente de UIView como UIStackView que simplifica mucho la auto-distribución). Al igual que con el cambio dinámico de UITableViewCells, toda la jerarquía de vistas debe tener restricciones configuradas, desde el contenedor más externo hasta la vista más interna. Eso incluye restricciones entre UICollectionViewCell y la vista secundaria inmediata

  2. Indique al flowlayout de su UICollectionView que dimensione automáticamente

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Marmoy
fuente
Tengo curiosidad, ¿cómo envolver el contenido de sus celdas en una UIView hará que el diseño automático funcione mejor? ¿Pensé que funcionaría mejor con una jerarquía más superficial?
James Heald
1
No mencioné el rendimiento, solo la simplicidad. Las celdas de tamaño automático solo funcionarán si tiene restricciones que abarcan todo el camino entre izquierda y derecha, y entre arriba y abajo. Lograr eso es más fácil cuando todas las vistas están envueltas en un solo contenedor. Si ese contenedor es un UIStackView, es más fácil que si es cualquier otro descendiente de UIView.
Marmoy
¿Puede decirme cómo establecer la altura constante (como establecer la altura 25, y el ancho se cambiará automáticamente) para UICollectionViewFlowLayoutAutomaticSize.
Svetoslav Atanasov
Usando Swift 4.1, Xcode me dice que se le UICollectionViewFlowLayout.automaticSizeha cambiado el nombre UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth
14
  1. Agregue flowLayout en viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Además, configure una UIView como mainContainer para su celda y agregue todas las vistas requeridas dentro de ella.

  3. Consulte este increíble tutorial alucinante para obtener más información: UICollectionView con celda de tamaño automático utilizando autolayout en iOS 9 y 10

dianakarenms
fuente
11

EDITAR 19/11/19: para iOS 13, solo use UICollectionViewCompositionalLayout con alturas estimadas. No pierdas tu tiempo lidiando con esta API rota.

Después de luchar con esto durante algún tiempo, noté que el cambio de tamaño no funciona para UITextViews si no deshabilita el desplazamiento:

let textView = UITextView()
textView.scrollEnabled = false
noobular
fuente
cuando intento desplazarme, la celda de la vista de colección no es uniforme. ¿Tienes alguna solución para esto?
Sathish Kumar Gurunathan
6

contentView misterio de anclaje:

En un caso extraño esto

    contentView.translatesAutoresizingMaskIntoConstraints = false

no funcionaría. Se agregaron cuatro anclas explícitas a contentView y funcionó.

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

y como siempre

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

en YourLayout: UICollectionViewFlowLayout

¿Quién sabe? Podría ayudar a alguien.

Crédito

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

Tropecé con la punta allí, nunca lo vi en ningún otro lugar en todos los artículos de los 1000 sobre esto.

Fattie
fuente
3

Hice una altura de celda dinámica de la vista de colección. Aquí está el repositorio de git hub .

Y, descubra por qué se llama más preferido una vez a PreferredLayoutAttributesFittingAttributes. En realidad, se llamará al menos 3 veces.

La imagen de registro de la consola: ingrese la descripción de la imagen aquí

1er preferidoLayoutAttributesFittingAttributes :

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

El layoutAttributes.frame.size.height es el estado actual 57.5 .

2do preferidoLayoutAttributesFittingAttributes :

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

La altura del marco de la celda cambió a 534.5 como se esperaba. Pero, la vista de colección sigue siendo de altura cero.

3er preferidoLayoutAttributesFittingAttributes :

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

Puede ver que la altura de la vista de colección cambió de 0 a 477 .

El comportamiento es similar a manejar desplazamiento:

1. Before self-sizing cell

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

3. Did changed self-sizing cell

Al principio, pensé que este método solo llamaba una vez. Así que codifiqué lo siguiente:

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

Esta línea:

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

hará que el sistema llame al bucle infinito y se bloquee la aplicación.

Cualquier cambio de tamaño, validará todas las celdas 'PreferredAttributesFittingAttributes' de todas las celdas una y otra vez hasta que las posiciones de cada celda (es decir, marcos) ya no cambien.

Yang Young
fuente
2

Además de las respuestas anteriores,

Solo asegúrese de establecer la propiedad estimadaItemSize de UICollectionViewFlowLayout en algún tamaño y no implemente sizeForItem: atIndexPath método de delegado .

Eso es.

Murat Yasar
fuente
1

Si implementa el método UICollectionViewDelegateFlowLayout:

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

Cuando llame collectionview performBatchUpdates:completion:, la altura del tamaño se usará en sizeForItemAtIndexPathlugar de preferredLayoutAttributesFittingAttributes .

El proceso de renderizado performBatchUpdates:completionpasará por el método preferredLayoutAttributesFittingAttributespero ignora los cambios.

Yang Young
fuente
He visto este mal comportamiento, pero solo cuando el tamaño estimado es cero. ¿Estás seguro de que estás configurando un tamaño estimado?
dgatwood
1

A quien pueda ayudar,

Tuve ese accidente desagradable si estimatedItemSizeestaba configurado. Incluso si devuelvo 0 en numberOfItemsInSection. Por lo tanto, las celdas en sí y su diseño automático no fueron la causa del bloqueo ... La colecciónView simplemente se bloqueó, incluso cuando estaba vacía, solo porqueestimatedItemSize estaba configurado para el autoajuste.

En mi caso, reorganicé mi proyecto, desde un controlador que contiene un collectionView a un collectionViewController, y funcionó.

Imagínate.

Jean Le Moignan
fuente
1
Intenta llamar collectionView.collectionViewLayout.invalidateLayout()después collectionView.reloadData().
chengsam
para ios10 e inferior, agregue este código `override func viewWillLayoutSubviews () {super.viewWillLayoutSubviews () if #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma
1

Para cualquiera que haya intentado todo sin suerte, esto es lo único que lo hizo funcionar para mí. Para las etiquetas multilínea dentro de la celda, intente agregar esta línea mágica:

label.preferredMaxLayoutWidth = 200

Más información: aquí

¡Salud!

HelloimDarius
fuente
0

El método de ejemplo anterior no se compila. Aquí hay una versión corregida (pero no comprobada si funciona o no).

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
}
usuario3246173
fuente
0

Actualiza más información:

  • Si usa flowLayout.estimatedItemSize, sugiera usar iOS8.3 versión posterior. Antes de iOS8.3, se bloqueará [super layoutAttributesForElementsInRect:rect];. El mensaje de error es

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

  • En segundo lugar, en la versión iOS8.x, la flowLayout.estimatedItemSizeconfiguración de inserción de sección diferente no funcionó. es decir, la función: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.

Yang Young
fuente
0

La solución comprende 4 pasos importantes:

  1. Habilitar el tamaño dinámico de la celda

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Habilite explícitamente el diseño automático y anclar contentViewa los bordes de la celda porque contentViewevita el auto dimensionamiento, dado que no tiene habilitado el diseño automático.
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. Establezca contentView.widthAnchor.constraint from collectionView(:cellForItemAt:)para limitar el ancho de contentView al ancho de collectionView.

Sucede de la siguiente manera; configuraremos la maxWidthvariable de la celda al ancho de CollectionView en collectionView(:cellForItemAt:)y en maxWidthel didSetmétodo, estableceremos widthAnchor.constant en 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
        }
    }

    ....
}

Como desea habilitar el autoajuste de UITextView, tiene un paso adicional para;

4. Calcule y establezca la alturaAnchor.constant de UITextView.

Entonces, cada vez que se establece el ancho de contentView ajustaremos la altura de UITextView a lo largo didSetde maxWidth.

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

Estos pasos te darán el resultado deseado. Aquí hay una esencia completa ejecutable

Gracias a Vadim Bulavin por su publicación de blog clara y esclarecedora - Autoedición de las celdas de la vista de colección: tutorial paso a paso

Harley
fuente
-2

Intenté usar estimatedItemSizepero había un montón de errores al insertar y eliminar celdas si estimatedItemSizeno era exactamente igual a la altura de la celda. Dejé de configurar estimatedItemSizee implementé celdas dinámicas utilizando un prototipo de celda. así es como se hace:

crea este protocolo:

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

implementar este protocolo en su costumbre 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
    }
}

ahora haga que su controlador se ajuste y UICollectionViewDelegateFlowLayout, en él, tenga este 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
}

se usará como una celda prototipo para enlazar datos y luego determinar cómo esos datos afectaron la dimensión en la que desea ser dinámico

finalmente, el UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)tiene que ser implementado:

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

y eso es. Devolver el tamaño de la celda de collectionView(:layout:sizeForItemAt:)esta manera evitando que tenga que usar estimatedItemSize, e insertar y eliminar celdas funciona perfectamente.

zapato
fuente