Parece que lo que está pidiendo es una forma de usar UICollectionView para producir un diseño como UITableView. Si eso es realmente lo que desea, la forma correcta de hacerlo es con una subclase UICollectionViewLayout personalizada (tal vez algo como SBTableLayout ).
Por otro lado, si realmente está preguntando si hay una forma limpia de hacer esto con el UICollectionViewFlowLayout predeterminado, entonces creo que no hay forma. Incluso con las celdas de tamaño propio de iOS8, no es sencillo. El problema fundamental, como usted dice, es que la maquinaria del diseño de flujo no proporciona forma de arreglar una dimensión y dejar que otra responda. (Además, incluso si pudiera, habría una complejidad adicional en torno a la necesidad de dos pasadas de diseño para dimensionar las etiquetas de varias líneas. Es posible que esto no se ajuste a la forma en que las celdas de tamaño propio desean calcular todo el tamaño a través de una llamada a systemLayoutSizeFittingSize).
Sin embargo, si aún desea crear un diseño similar a una vista de tabla con un diseño de flujo, con celdas que determinan su propio tamaño y responden naturalmente al ancho de la vista de la colección, por supuesto que es posible. Todavía queda el camino desordenado. Lo he hecho con una "celda de tamaño", es decir, una UICollectionViewCell no mostrada que el controlador guarda solo para calcular el tamaño de las celdas.
Este enfoque consta de dos partes. La primera parte es que el delegado de la vista de colección calcule el tamaño de celda correcto, tomando el ancho de la vista de colección y usando la celda de tamaño para calcular la altura de la celda.
En su UICollectionViewDelegateFlowLayout, implementa un método como este:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
Esto hará que la vista de la colección establezca el ancho de la celda en función de la vista de la colección adjunta, pero luego le pedirá a la celda de tamaño que calcule la altura.
La segunda parte es hacer que la celda de tamaño use sus propias restricciones AL para calcular la altura. Esto puede ser más difícil de lo que debería ser, debido a la forma en que los UILabel de múltiples líneas requieren efectivamente un proceso de diseño de dos etapas. El trabajo se realiza en el método preferredLayoutSizeFittingSize
, que es así:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(Este código está adaptado del código de muestra de Apple del código de muestra de la sesión WWDC2014 en "Vista de colección avanzada").
Un par de puntos para notar. Está usando layoutIfNeeded () para forzar el diseño de toda la celda, con el fin de calcular y establecer el ancho de la etiqueta. Pero eso no es suficiente. Creo que también debe configurar preferredMaxLayoutWidth
para que la etiqueta use ese ancho con Diseño automático. Y solo entonces se puede usar systemLayoutSizeFittingSize
para que la celda calcule su altura teniendo en cuenta la etiqueta.
¿Me gusta este enfoque? ¡¡No!! Se siente demasiado complejo y hace el diseño dos veces. Pero siempre que el rendimiento no se convierta en un problema, prefiero realizar el diseño dos veces en tiempo de ejecución que tener que definirlo dos veces en el código, que parece ser la única otra alternativa.
Mi esperanza es que eventualmente las células de tamaño automático funcionen de manera diferente y todo esto se volverá mucho más simple.
Proyecto de ejemplo que lo muestra en funcionamiento.
Pero, ¿por qué no usar simplemente células de tamaño propio?
En teoría, las nuevas instalaciones de iOS8 para "celdas de tamaño propio" deberían hacer esto innecesario. Si ha definido una celda con Auto Layout (AL), entonces la vista de colección debería ser lo suficientemente inteligente como para permitir que se dimensione y se distribuya correctamente. En la práctica, no he visto ningún ejemplo que haya logrado que esto funcione con etiquetas de varias líneas. Creo que esto se debe en parte a que el mecanismo de la celda de tamaño automático todavía tiene errores.
Pero apuesto a que se debe principalmente a la complicación habitual del diseño automático y las etiquetas, que es que UILabels requiere un proceso de diseño básicamente de dos pasos. No tengo claro cómo se pueden realizar ambos pasos con células de tamaño propio.
Y como dije, este es realmente un trabajo para un diseño diferente. Es parte de la esencia del diseño de flujo que coloca las cosas que tienen un tamaño, en lugar de fijar un ancho y les permite elegir su altura.
¿Y qué hay de favoriteLayoutAttributesFittingAttributes:?
El preferredLayoutAttributesFittingAttributes:
método es una pista falsa, creo. Eso solo está ahí para ser utilizado con el nuevo mecanismo de celda de tamaño propio. Entonces, esta no es la respuesta mientras ese mecanismo no sea confiable.
¿Y qué pasa con systemlayoutSizeFittingSize :?
Tienes razón, los documentos son confusos.
Los documentos en systemLayoutSizeFittingSize:
y systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
ambos sugieren que solo debe pasar UILayoutFittingCompressedSize
y UILayoutFittingExpandedSize
como targetSize
. Sin embargo, la propia firma del método, los comentarios del encabezado y el comportamiento de las funciones indican que están respondiendo al valor exacto del targetSize
parámetro.
De hecho, si configura el UICollectionViewFlowLayoutDelegate.estimatedItemSize
, para habilitar el nuevo mecanismo de celda de auto-tamaño, ese valor parece pasar como targetSize. Y UILabel.systemLayoutSizeFittingSize
parece devolver exactamente los mismos valores que UILabel.sizeThatFits
. Esto es sospechoso, dado que systemLayoutSizeFittingSize
se supone que el argumento to es un objetivo aproximado y sizeThatFits:
se supone que el argumento to tiene un tamaño de circunscripción máximo.
Más recursos
Si bien es triste pensar que un requisito tan rutinario debería requerir "recursos de investigación", creo que sí. Buenos ejemplos y discusiones son: