UICollectionView dentro de una UITableViewCell – altura dinámica?

Una de nuestras pantallas de aplicaciones requiere que UICollectionView un UICollectionView dentro de una UITableViewCell . Este UICollectionView tendrá un número dynamic de elementos, lo que dará como resultado una altura que también se debe calcular dinámicamente. Sin embargo, estoy UICollectionView problemas para calcular la altura del UICollectionView incrustado.

Nuestro UIViewController general se creó en Storyboards y hace uso del diseño automático. Pero no sé cómo boost dinámicamente la altura de UITableViewCell función de la altura de UICollectionView .

¿Alguien puede dar algunos consejos o consejos sobre cómo lograr esto?

La respuesta correcta es , PUEDES hacer esto.

Me encontré con este problema hace algunas semanas. En realidad es más fácil de lo que piensas. Coloque sus células en NIB (o guiones gráficos) y péguelos para que el diseño automático haga todo el trabajo

Dada la siguiente estructura:

TableView

TableViewCell

CollectionView

CollectionViewCell

CollectionViewCell

CollectionViewCell

[… número variable de celdas o diferentes tamaños de celda]

La solución es decirle a la distribución automática que calcule primero los tamaños de collectionViewCell, luego la vista de colección contentSize, y la use como el tamaño de su celda. Este es el método UIView que “hace la magia”:

 -(void)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority 

Debe establecer aquí el tamaño de TableViewCell, que en su caso es ContentSize de CollectionView.

CollectionViewCell

En CollectionViewCell , tiene que indicarle a la celda el diseño cada vez que cambie el modelo (por ejemplo, si configura un UILabel con un texto, entonces la celda tiene que volver a diseñarse).

 - (void)bindWithModel:(id)model { // Do whatever you may need to bind with your data and // tell the collection view cell's contentView to resize [self.contentView setNeedsLayout]; } // Other stuff here... 

TableViewCell

TableViewCell hace la magia. Tiene una salida para su collectionView, habilita el diseño automático para las celdas collectionView utilizando estimatedItemSize de UICollectionViewFlowLayout .

Entonces, el truco es establecer el tamaño de su celda tableView en el método systemLayoutSizeFittingSize …. (NOTA: iOS8 o posterior)

NOTA: Intenté usar el método de altura de la celda delegada de tableView -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath .pero es demasiado tarde para que el sistema de diseño automático calcule CollectionView contentSize y, a veces, puede encontrar celdas de tamaño incorrecto.

 @implementation TableCell - (void)awakeFromNib { [super awakeFromNib]; UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; // Configure the collectionView flow.minimumInteritemSpacing = ...; // This enables the magic of auto layout. // Setting estimatedItemSize different to CGSizeZero // on flow Layout enables auto layout for collectionView cells. // https://developer.apple.com/videos/play/wwdc2014-226/ flow.estimatedItemSize = CGSizeMake(1, 1); // Disable the scroll on your collection view // to avoid running into multiple scroll issues. [self.collectionView setScrollEnabled:NO]; } - (void)bindWithModel:(id)model { // Do your stuff here to configure the tableViewCell // Tell the cell to redraw its contentView [self.contentView layoutIfNeeded]; } // THIS IS THE MOST IMPORTANT METHOD // // This method tells the auto layout // You cannot calculate the collectionView content size in any other place, // because you run into race condition issues. // NOTE: Works for iOS 8 or later - (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority { // With autolayout enabled on collection view's cells we need to force a collection view relayout with the shown size (width) self.collectionView.frame = CGRectMake(0, 0, targetSize.width, MAXFLOAT); [self.collectionView layoutIfNeeded]; // If the cell's size has to be exactly the content // Size of the collection View, just return the // collectionViewLayout's collectionViewContentSize. return [self.collectionView.collectionViewLayout collectionViewContentSize]; } // Other stuff here... @end 

TableViewController

Recuerde habilitar el sistema de diseño automático para las celdas tableView en su TableViewController :

 - (void)viewDidLoad { [super viewDidLoad]; // Enable automatic row auto layout calculations self.tableView.rowHeight = UITableViewAutomaticDimension; // Set the estimatedRowHeight to a non-0 value to enable auto layout. self.tableView.estimatedRowHeight = 10; } 

CRÉDITO: @rbarbera ayudó a resolver esto

Creo que mi camino es mucho más simple, luego propuesto por @PabloRomeu.

Paso 1. Cree una salida de la UITableViewCell subclass UICollectionView a UITableViewCell subclass , donde se ubica UICollectionView . Vamos, su nombre será collectionView

Paso 2. Agregue IB para la UICollectionView altura UICollectionView y cree una salida a la UITableViewCell subclass también. Vamos, su nombre será collectionViewHeight .

Paso 3. En tableView:cellForRowAtIndexPath: add code:

 // deque a cell cell.frame = tableView.bounds; [cell layoutIfNeeded]; [cell.collectionView reloadData]; cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height; 

Tanto las vistas de tabla como las vistas de colección son subclases UIScrollView y, por lo tanto, no les gusta incrustarse en otra vista de desplazamiento mientras intentan calcular tamaños de contenido, reutilizar celdas, etc.

Te recomiendo que uses solo una vista de colección para todos tus propósitos.

Puede dividirlo en secciones y “tratar” el diseño de algunas secciones como una vista de tabla y otras como una vista de colección. Después de todo, no hay nada que no pueda lograr con una vista de colección que pueda con una vista de tabla.

Si tiene un diseño de cuadrícula básico para sus “partes” de vista de colección, también puede usar celdas de tabla regulares para manejarlas. Aun así, si no necesita soporte para iOS 5, debería usar mejor las vistas de colección.

Una alternativa a la solución de Pablo Romeu es personalizar UICollectionView, en lugar de hacer el trabajo en la celda de vista de tabla.

El problema subyacente es que, de forma predeterminada, una vista de colección no tiene un tamaño intrínseco y, por lo tanto, no puede informar el diseño automático de las dimensiones que se utilizarán. Puede remediarlo creando una subclase personalizada que devuelva un tamaño intrínseco útil.

Cree una subclase de UICollectionView y anule los siguientes métodos

 override func intrinsicContentSize() -> CGSize { self.layoutIfNeeded() var size = super.contentSize if size.width == 0 || size.height == 0 { // return a default size size = CGSize(width: 600, height:44) } return size } override func reloadData() { super.reloadData() self.layoutIfNeeded() self.invalidateIntrinsicContentSize() } 

(También debe anular los métodos relacionados: reloadSections, reloadItemsAtIndexPaths de forma similar a reloadData ())

Llamar layoutIfNeeded obliga a la vista de recostackción a recalcular el tamaño del contenido, que luego puede usarse como el nuevo tamaño intrínseco.

Además, necesita manejar explícitamente los cambios en el tamaño de la vista (por ejemplo, en la rotación del dispositivo) en el controlador de vista de tabla

  override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) dispatch_async(dispatch_get_main_queue()) { self.tableView.reloadData() } } 

Pondría un método estático en la clase de vista de colección que devolverá un tamaño basado en el contenido que tendrá. Luego use ese método en heightForRowAtIndexPath para devolver el tamaño correcto.

También tenga en cuenta que puede obtener un comportamiento extraño cuando incorpora estos tipos de viewControllers. Lo hice una vez y tuve algunos problemas de memoria extraños que nunca funcionó.

Tal vez mi variante sea útil; He estado decidiendo esta tarea durante las últimas dos horas. No pretendo que sea 100% correcto u óptimo, pero mi habilidad es muy pequeña todavía y me gustaría escuchar los comentarios de los expertos. Gracias. Una nota importante: esto funciona para la tabla estática, está especificado por mi trabajo actual. Entonces, todo lo que uso es viewWillLayoutSubviews de tableView . Y un poco más

 private var iconsCellHeight: CGFloat = 500 func updateTable(table: UITableView, withDuration duration: NSTimeInterval) { UIView.animateWithDuration(duration, animations: { () -> Void in table.beginUpdates() table.endUpdates() }) } override func viewWillLayoutSubviews() { if let iconsCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 1)) as? CategoryCardIconsCell { let collectionViewContentHeight = iconsCell.iconsCollectionView.contentSize.height if collectionViewContentHeight + 17 != iconsCellHeight { iconsCellHeight = collectionViewContentHeight + 17 updateTable(tableView, withDuration: 0.2) } } } override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { switch (indexPath.section, indexPath.row) { case ... case (1,0): return iconsCellHeight default: return tableView.rowHeight } } 
  1. Sé que el collectionView está ubicado en la primera fila de la segunda sección;
  2. Deje que la altura de la fila sea 17 p. más grande que su altura de contenido;
  3. iconsCellHeight es un número aleatorio a medida que se inicia el progtwig (lo sé, que en la forma de retrato tiene que ser exactamente 392, pero no es importante). Si el contenido de collectionView + 17 no es igual a este número, cambie su valor. La próxima vez en esta situación, la condición es FALSA;
  4. Después de todo actualizar el tableView. En mi caso es la combinación de dos operaciones (para una buena actualización de las filas extendidas);
  5. Y, por supuesto, en heightForRowAtIndexPath agrega una fila al código.

El enfoque más fácil que he tenido, hasta ahora, Créditos para @igor responder arriba,

En tu clase tableviewcell simplemente inserta esto

 override func layoutSubviews() { self.collectionViewOutlet.constant = self.postPoll.collectionViewLayout.collectionViewContentSize.height } 

y por supuesto, cambie la salida de la colección con su salida en la clase de la celda

La respuesta de Pablo Romeu anterior ( https://stackoverflow.com/a/33364092/2704206 ) me ayudó muchísimo con mi problema. Sin embargo, tenía que hacer algunas cosas de manera diferente para que esto funcionara para mi problema. En primer lugar, no tuve que llamar a layoutIfNeeded() tanta frecuencia. Solo tuve que llamarlo a collectionView en la función systemLayoutSizeFitting .

En segundo lugar, tenía restricciones de diseño automático en mi vista de colección en la celda de vista de tabla para darle algo de relleno. Así que tuve que restar los márgenes targetSize.width y finales de targetSize.width al establecer el ancho de collectionView.frame . También tuve que agregar los márgenes superior e inferior al valor de retorno CGSize height.

Para obtener estas constantes de restricción, tuve la opción de crear salidas a las restricciones, codificar sus constantes o buscarlas por un identificador. Decidí ir con la tercera opción para hacer que mi clase de celda de vista de tabla personalizada sea fácilmente reutilizable. Al final, esto era todo lo que necesitaba para ponerlo en funcionamiento:

 class CollectionTableViewCell: UITableViewCell { // MARK: - // MARK: Properties @IBOutlet weak var collectionView: UICollectionView! { didSet { collectionViewLayout?.estimatedItemSize = CGSize(width: 1, height: 1) selectionStyle = .none } } var collectionViewLayout: UICollectionViewFlowLayout? { return collectionView.collectionViewLayout as? UICollectionViewFlowLayout } // MARK: - // MARK: UIView functions override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize { collectionView.layoutIfNeeded() let topConstraintConstant = contentView.constraint(byIdentifier: "topAnchor")?.constant ?? 0 let bottomConstraintConstant = contentView.constraint(byIdentifier: "bottomAnchor")?.constant ?? 0 let trailingConstraintConstant = contentView.constraint(byIdentifier: "trailingAnchor")?.constant ?? 0 let leadingConstraintConstant = contentView.constraint(byIdentifier: "leadingAnchor")?.constant ?? 0 collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width - trailingConstraintConstant - leadingConstraintConstant, height: 1) let size = collectionView.collectionViewLayout.collectionViewContentSize let newSize = CGSize(width: size.width, height: size.height + topConstraintConstant + bottomConstraintConstant) return newSize } } 

Como función auxiliar para recuperar una restricción por identificador, agrego la siguiente extensión:

 extension UIView { func constraint(byIdentifier identifier: String) -> NSLayoutConstraint? { return constraints.first(where: { $0.identifier == identifier }) } } 

NOTA: Necesitará establecer el identificador en estas restricciones en su guión gráfico, o donde sea que se creen. A menos que tengan una constante 0, entonces no importa. Además, como en la respuesta de Pablo, deberá usar UICollectionViewFlowLayout como el diseño de su vista de colección. Finalmente, asegúrate de vincular la collectionView IBOutlet con tu guión gráfico.

Con la celda de vista de tabla personalizada anterior, ahora puedo crear una subclase en cualquier otra celda de vista de tabla que necesite una vista de colección y hacer que implemente los protocolos UICollectionViewDelegateFlowLayout y UICollectionViewDataSource . Espero que esto sea útil para otra persona!

La solución de Pablo no funcionó muy bien para mí, tenía efectos visuales extraños (la colección View no se ajustaba correctamente).

Lo que funcionó fue ajustar la restricción de altura de collectionView (como NSLayoutConstraint) a collectionView layoutSubviews() durante layoutSubviews() . Este es el método llamado cuando se aplica autolayout a la celda.

 // Constraint on the collectionView height in the storyboard. Priority set to 999. @IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint! // Method called by autolayout to layout the subviews (including the collectionView). // This is triggered with 'layoutIfNeeded()', or by the viewController // (happens between 'viewWillLayoutSubviews()' and 'viewDidLayoutSubviews()'. override func layoutSubviews() { collectionViewHeightConstraint.constant = collectionView.contentSize.height super.layoutSubviews() } // Call `layoutIfNeeded()` when you update your UI from the model to trigger 'layoutSubviews()' private func updateUI() { layoutIfNeeded() } 

Me enfrentaba al mismo problema recientemente y casi probé todas las soluciones en las respuestas, algunas funcionaron y otras no. Mi principal preocupación sobre el enfoque de @PabloRomeu es que si tiene otros contenidos en la celda (que no sean la vista de colección) Tendrás que calcular sus alturas y las alturas de sus restricciones y devolver el resultado para obtener el diseño automático correcto y no me gusta calcular las cosas manualmente en mi código. Así que aquí está la solución que funcionó bien para mí sin hacer ningún cálculo manual en mi código.

en cellForRow: atIndexPath de la vista de tabla, hago lo siguiente:

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //do dequeue stuff //initialize the the collection view data source with the data cell.fame = CGRect.zero cell.layoutIfNeeded() return cell } 

Creo que lo que sucede aquí es que fuerzo a la celda de tabla para ajustar su altura después de que se ha calculado la altura de vista de la colección (después de proporcionar la fecha collectionView a la fuente de datos)

En su UITableViewDelegate:

 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return ceil(itemCount/4.0f)*collectionViewCellHeight; } 

Sustituir itemCount y CollectionViewCellHeight con los valores reales. Si tienes una matriz de matrices, itemCount podría ser:

 self.items[indexPath.row].count 

O lo que sea.

1.Crear celda ficticia.
2.Utilice el método collectionViewContentSize en UICollectionViewLayout de UICollectionView utilizando los datos actuales.

Puede calcular la altura de la colección en función de sus propiedades, como itemSize , sectionInset , minimumInteritemSpacing , minimumInteritemSpacing , si su collectionViewCell tiene el borde de una regla.