with automatic ios uicollectionview autolayout uicollectionviewcell

ios - automatic - UICollectionView Celdas de tamaño automático con diseño automático



collectionview with autolayout (15)

Actualizado para Swift 5

preferredLayoutAttributesFittingAttributes renombrado a preferredLayoutAttributesFitting y usar el tamaño 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 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 funcionen las celdas de tamaño automático.

1. Establezca estimatedItemSize en UICollectionViewFlowLayout

El diseño del flujo se volverá dinámico en la naturaleza una vez que establezca la propiedad estimatedItemSize .

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Agregue soporte para dimensionar en 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 entraré en detalles sobre esto, ya que hay una publicación SO brillante 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 configurados como los límites de la celda cuando la celda se carga (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.

Implemente preferredLayoutAttributesFittingAttributes en su celda personalizada

Cuando se llama a esta función, su vista ya se ha configurado con contenido (es decir, se ha llamado a cellForItem ). 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 here ). Cuando implemente preferredLayoutAttributesFittingAttributes , 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 invalidar esto cada vez que reutilice la celda o cambie su contenido como lo hice con la propiedad isHeightCalculated .

Experimenta tu diseño

En este punto, debería tener celdas dinámicas "funcionales" en su collectionView. Todavía no he encontrado la solución inmediata suficiente durante mis pruebas, así que siéntase libre de comentar. Todavía se siente como UITableView 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 puede hacer que su celda anule el captador traitCollection y devuelva los rasgos apropiados. Tu decides.

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

Estoy tratando de hacer que UICollectionViewCells funcione con Auto Layout, pero parece que no puedo hacer que las celdas se UICollectionViewCells al contenido. Tengo problemas para entender 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:

  • UICollectionViewCell personalizado con un UITextView en su contentView.
  • El desplazamiento hacia UITextView está deshabilitado.
  • La restricción horizontal de contentView es: "H: | [_textView (320)]", es decir, UITextView está UITextView 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, UITextView fijado en la parte superior de la celda.
  • UITextView tiene una restricción de altura establecida en una constante que los informes de UITextView se ajustarán al texto.

Esto es lo que parece con el fondo de la celda establecido en rojo y el fondo UITextView establecido en Azul:

Puse el proyecto con el que he estado jugando en GitHub here .


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 1000 sobre esto.


  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


A quien pueda ayudar,

Tuve ese accidente desagradable si se estableció estimatedItemSize . Incluso si numberOfItemsInSection 0 en numberOfItemsInSection . Por lo tanto, las celdas en sí mismas y su diseño automático no fueron la causa del bloqueo ... El collectionView simplemente se bloqueó, incluso cuando estaba vacío, solo porque estimatedItemSize estaba configurada para auto-dimensionamiento.

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

Imagínate.


Actualiza más información:

  • Si usa flowLayout.estimatedItemSize , sugiera usar la versión posterior de iOS8.3. 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, flowLayout.estimatedItemSize hará que la configuración de inserción de sección diferente no funcione. es decir, función: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:


Además de las respuestas anteriores,

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

Eso es.


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 única 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 docs :

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


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


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 }


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, lo que simplifica mucho la distribución automática). Al igual que con el cambio de tamaño 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


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 del 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 = UICollectionViewFlowLayoutAutomaticSize } }


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 PreferidLayoutAttributesFittingAttributes. En realidad, se llamará al menos 3 veces.

La imagen de registro de la consola:

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 tamaño cambiado, 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.


Intenté usar estimatedItemSize pero hubo un montón de errores al insertar y eliminar celdas si el estimatedItemSize no era exactamente igual a la altura de la celda. Dejé de establecer el tamaño de artículo estimatedItemSize e implementé celdas dinámicas mediante el uso de una celda prototipo. así es como se hace:

crea este protocolo:

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

implemente este protocolo en su UICollectionViewCell personalizada:

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 a UICollectionViewDelegateFlowLayout y, 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, se debe UICollectionViewDelegateFlowLayout''s el UICollectionViewDelegateFlowLayout''s collectionView(:layout:sizeForItemAt:) :

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 en collectionView(:layout:sizeForItemAt:) de esta manera evitando que tenga que usar el tamaño de artículo estimatedItemSize , e insertar y eliminar celdas funciona perfectamente.


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: here

¡Salud!


Si implementa el método UICollectionViewDelegateFlowLayout:

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

Cuando llame a collectionview performBatchUpdates:completion: la altura del tamaño usará sizeForItemAtIndexPath lugar de preferredLayoutAttributesFittingAttributes .

El proceso de representación de performBatchUpdates:completion pasará por el método preferredLayoutAttributesFittingAttributes pero ignorará sus cambios.