Uso de Diseño automático en UITableView para diseños dynamics de celdas y alturas de fila variables

¿Cómo se usa el diseño automático dentro de UITableViewCell en una vista de tabla para permitir que el contenido y las subvistas de cada celda determinen la altura de la fila (en sí misma / automáticamente), a la vez que se mantiene el rendimiento de desplazamiento suave?

TL; DR: ¿No le gusta leer? Salta directamente a los proyectos de muestra en GitHub:

  • Proyecto de ejemplo iOS 8 : Requiere iOS 8
  • Proyecto de ejemplo iOS 7 : funciona en iOS 7+

Descripción Conceptual

Los primeros 2 pasos a continuación son aplicables independientemente de las versiones de iOS para las que esté desarrollando.

1. Configurar y agregar restricciones

En su subclase UITableViewCell , agregue restricciones para que las subvistas de la celda tengan sus bordes fijados a los bordes del contenido de la celda (lo que es más importante para los bordes superior e inferior). NOTA: no anote las subvistas a la celda misma; solo al contenido de la celda! Permita que el tamaño del contenido intrínseco de estas subvistas controle la altura de la vista de contenido de la celda de la vista de tabla asegurándose de que la resistencia a la compresión del contenido y las restricciones de contenido en la dimensión vertical para cada subvista no sean anuladas por las restricciones de mayor prioridad que haya agregado. ( ¿Huh? Haz clic aquí )

Recuerde, la idea es tener las subvistas de la celda conectadas verticalmente a la vista de contenido de la celda para que puedan “ejercer presión” y hacer que la vista de contenido se expanda para ajustarse a ellas. Usando una celda de ejemplo con algunas subvistas, aquí hay una ilustración visual de lo que algunas (¡no todas!) De sus restricciones deberían tener:

Ejemplo de ilustración de restricciones en una celda de vista de tabla.

Puede imaginarse que a medida que se agregue más texto a la etiqueta del cuerpo de varias líneas en la celda de ejemplo anterior, tendrá que crecer verticalmente para ajustarse al texto, lo que efectivamente forzará a la celda a crecer en altura. (¡Por supuesto, necesita tener las restricciones correctas para que esto funcione correctamente!)

Obtener sus restricciones correctas es definitivamente la parte más difícil y más importante para lograr que las alturas dinámicas de las celdas funcionen con Diseño automático. Si comete un error aquí, podría evitar que todo lo demás funcione, ¡así que tómese su tiempo! Recomiendo configurar sus restricciones en el código porque sabe exactamente qué restricciones se agregan en dónde, y es mucho más fácil de depurar cuando las cosas van mal. Agregar restricciones en el código puede ser tan fácil y significativamente más poderoso que Interface Builder utilizando anclajes de diseño, o una de las fantásticas API de código abierto disponibles en GitHub.

  • Si agrega restricciones en el código, debe hacerlo una vez desde el método updateConstraints de su subclase UITableViewCell. Tenga en cuenta que updateConstraints posible llamar a updateConstraints más de una vez, por lo que para evitar agregar las mismas restricciones más de una vez, asegúrese de ajustar el código de adición de restricciones en updateConstraints en una verificación de una propiedad booleana como didSetupConstraints (que estableció en YES después de ejecute su código de restricción de restricción una vez). Por otro lado, si tiene un código que actualiza las restricciones existentes (como el ajuste de la propiedad constant en algunas restricciones), colóquelo en updateConstraints pero fuera de la comprobación de didSetupConstraints para que pueda ejecutarse cada vez que se llame al método.

2. Determinar identificadores únicos de reutilización de celdas de vista de tabla

Para cada conjunto único de restricciones en la celda, use un identificador de reutilización de celda único. En otras palabras, si sus celdas tienen más de un diseño único, cada diseño único debería recibir su propio identificador de reutilización. (Una buena sugerencia de que necesita usar un nuevo identificador de reutilización es cuando la variante de su celda tiene un número diferente de subvistas, o las subvistas se organizan de una manera distinta).

Por ejemplo, si mostrara un mensaje de correo electrónico en cada celda, podría tener 4 diseños únicos: mensajes con solo un asunto, mensajes con un sujeto y un cuerpo, mensajes con un asunto y una fotografía adjunta, y mensajes con un tema, cuerpo y foto adjunta. Cada diseño tiene restricciones completamente diferentes requeridas para lograrlo, por lo que una vez que se inicializa la celda y se agregan las restricciones para uno de estos tipos de celda, la celda debe obtener un identificador único de reutilización específico para ese tipo de celda. Esto significa que cuando dequeue una celda para su reutilización, las restricciones ya se han agregado y están listas para usar ese tipo de celda.

Tenga en cuenta que debido a las diferencias en el tamaño del contenido intrínseco, las celdas con las mismas restricciones (tipo) aún pueden tener diferentes alturas. No confunda diseños fundamentalmente diferentes (diferentes restricciones) con diferentes marcos de vista calculados (resueltos de restricciones idénticas) debido a diferentes tamaños de contenido.

  • No agregue celdas con conjuntos de restricciones completamente diferentes a la misma agrupación de reutilización (es decir, use el mismo identificador de reutilización) y luego intente eliminar las restricciones anteriores y configure nuevas restricciones desde cero después de cada dequeue. El motor interno de diseño automático no está diseñado para manejar cambios a gran escala en las limitaciones, y verá problemas de rendimiento masivo.

Para iOS 8 – Células de tamaño automático

3. Habilitar la estimación de altura de fila

Para habilitar las celdas de vista de tabla de tamaño propio, debe establecer la propiedad rowHeight de la vista de tabla en UITableViewAutomaticDimension. También debe asignar un valor a la propiedad estimatedRowHeight. Tan pronto como se establecen estas dos propiedades, el sistema utiliza el diseño automático para calcular la altura real de la fila

Apple: trabajando con celdas de vista de tabla de tamaño automático

Con iOS 8, Apple ha internalizado gran parte del trabajo que previamente tenía que implementar antes de iOS 8. Para permitir que el mecanismo de celda de auto-tamaño funcione, primero debe establecer la propiedad rowHeight en la vista de tabla para el constante UITableViewAutomaticDimension . Entonces, simplemente necesita habilitar la estimación de altura de fila estableciendo la propiedadRowHeight estimatedRowHeight la vista de tabla en un valor distinto de cero, por ejemplo:

 self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is 

Lo que hace es proporcionar a la vista de tabla una estimación temporal / marcador de posición para las alturas de filas de las celdas que todavía no están en pantalla. Luego, cuando estas celdas estén a punto de desplazarse por la pantalla, se calculará la altura real de la fila. Para determinar la altura real de cada fila, la vista de tabla pregunta automáticamente a cada celda qué altura debe contentView su contentView en función del ancho fijo conocido de la vista de contenido (que se basa en el ancho de la vista de tabla, menos cualquier elemento adicional como una sección vista de índice o accesorio) y las restricciones de diseño automático que ha agregado a la vista y las subvistas de contenido de la celda. Una vez que se ha determinado la altura real de la celda, la altura estimada anterior para la fila se actualiza con la nueva altura real (y cualquier ajuste al contentSize / contentOffset de la vista de tabla se realiza según sea necesario para usted).

En general, la estimación que proporciona no tiene que ser muy precisa: solo se utiliza para ajustar correctamente el tamaño del indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo ajustando el indicador de desplazamiento para estimaciones incorrectas a medida que desplazarse por las celdas en pantalla. Debe establecer la propiedad estimatedRowHeight en la vista de tabla (en viewDidLoad o similar) a un valor constante que sea la altura de fila “promedio”. Solo si las alturas de sus filas tienen una variabilidad extrema (por ejemplo, difieren en un orden de magnitud) y nota que el indicador de desplazamiento “salta” mientras se desplaza debería preocuparse de implementar tableView:estimatedHeightForRowAtIndexPath: para hacer el cálculo mínimo requerido para devolver un cálculo más preciso cada fila.

Para compatibilidad con iOS 7 (implementando el tamaño automático de la celda)

3. Haz un pase de diseño y obtén la altura de la celda

Primero, crea una instancia fuera de pantalla de una celda de vista de tabla, una instancia para cada identificador de reutilización , que se usa estrictamente para cálculos de altura. (Fuera de pantalla significa que la referencia de celda se almacena en una propiedad / ivar en el controlador de vista y nunca volvió de tableView:cellForRowAtIndexPath: para que la vista de tabla se visualice en pantalla). A continuación, la celda debe configurarse con el contenido exacto (por ejemplo, texto, imágenes, etc.) que se mantendría si se mostrara en la vista de tabla.

Luego, obligue a la celda a diseñar de inmediato sus subvistas, y luego use el método UITableViewCell systemLayoutSizeFittingSize: en el UITableViewCell de contentView para averiguar cuál es la altura requerida de la celda. Use UILayoutFittingCompressedSize para obtener el tamaño más pequeño requerido para todos los contenidos de la celda. La altura puede luego ser devuelta desde tableView:heightForRowAtIndexPath: delegate method.

4. Utilice alturas de fila estimadas

Si su vista de tabla tiene más de un par de docenas de filas, encontrará que al hacer la resolución de restricción Diseño automático puede atascarse rápidamente en el hilo principal al cargar por primera vez la vista de tabla, ya que se tableView:heightForRowAtIndexPath: en cada fila en la primera carga (para calcular el tamaño del indicador de desplazamiento).

A partir de iOS 7, puede (y debería) utilizar la propiedad estimatedRowHeight en la vista de tabla. Lo que hace es proporcionar a la vista de tabla una estimación temporal / marcador de posición para las alturas de filas de las celdas que todavía no están en pantalla. Luego, cuando estas celdas estén a punto de desplazarse por la pantalla, se calculará la altura real de la fila (llamando a tableView:heightForRowAtIndexPath: y la altura estimada actualizada con la actual.

En general, la estimación que proporciona no tiene que ser muy precisa: solo se utiliza para ajustar correctamente el tamaño del indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo ajustando el indicador de desplazamiento para estimaciones incorrectas a medida que desplazarse por las celdas en pantalla. Debe establecer la propiedad estimatedRowHeight en la vista de tabla (en viewDidLoad o similar) a un valor constante que sea la altura de fila “promedio”. Solo si las alturas de sus filas tienen una variabilidad extrema (por ejemplo, difieren en un orden de magnitud) y nota que el indicador de desplazamiento “salta” mientras se desplaza debería preocuparse de implementar tableView:estimatedHeightForRowAtIndexPath: para hacer el cálculo mínimo requerido para devolver un cálculo más preciso cada fila.

5. (Si es necesario) Agregar caché de altura de fila

Si ya ha hecho todo lo anterior y aún encuentra que el rendimiento es inaceptablemente lento al hacer la resolución de restricciones en tableView:heightForRowAtIndexPath: desafortunadamente necesitará implementar algún almacenamiento en caché para alturas de celdas. (Este es el enfoque sugerido por los ingenieros de Apple). La idea general es dejar que el motor de Diseño automático resuelva las restricciones por primera vez, luego almacenar en caché la altura calculada para esa celda y usar el valor almacenado en caché para todas las solicitudes futuras para la altura de esa celda. El truco, por supuesto, es asegurarse de borrar la altura almacenada en caché de una celda cuando ocurre cualquier cosa que pueda cambiar la altura de la celda; principalmente, cuando el contenido de esa celda cambia o cuando ocurren otros eventos importantes (como el ajuste del usuario). el control deslizante de tamaño de texto de tipo dynamic).

Código de muestra genérico iOS 7 (con muchos comentarios jugosos)

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // Determine which reuse identifier should be used for the cell at this // index path, depending on the particular layout required (you may have // just one, or may have many). NSString *reuseIdentifier = ...; // Dequeue a cell for the reuse identifier. // Note that this method will init and return a new cell if there isn't // one available in the reuse pool, so either way after this line of // code you will have a cell with the correct constraints ready to go. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; // Configure the cell with content for the given indexPath, for example: // cell.textLabel.text = someTextForThisCell; // ... // Make sure the constraints have been set up for this cell, since it // may have just been created from scratch. Use the following lines, // assuming you are setting up constraints from within the cell's // updateConstraints method: [cell setNeedsUpdateConstraints]; [cell updateConstraintsIfNeeded]; // If you are using multi-line UILabels, don't forget that the // preferredMaxLayoutWidth needs to be set correctly. Do it at this // point if you are NOT doing it within the UITableViewCell subclass // -[layoutSubviews] method. For example: // cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds); return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // Determine which reuse identifier should be used for the cell at this // index path. NSString *reuseIdentifier = ...; // Use a dictionary of offscreen cells to get a cell for the reuse // identifier, creating a cell and storing it in the dictionary if one // hasn't already been added for the reuse identifier. WARNING: Don't // call the table view's dequeueReusableCellWithIdentifier: method here // because this will result in a memory leak as the cell is created but // never returned from the tableView:cellForRowAtIndexPath: method! UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier]; if (!cell) { cell = [[YourTableViewCellClass alloc] init]; [self.offscreenCells setObject:cell forKey:reuseIdentifier]; } // Configure the cell with content for the given indexPath, for example: // cell.textLabel.text = someTextForThisCell; // ... // Make sure the constraints have been set up for this cell, since it // may have just been created from scratch. Use the following lines, // assuming you are setting up constraints from within the cell's // updateConstraints method: [cell setNeedsUpdateConstraints]; [cell updateConstraintsIfNeeded]; // Set the width of the cell to match the width of the table view. This // is important so that we'll get the correct cell height for different // table view widths if the cell's height depends on its width (due to // multi-line UILabels word wrapping, etc). We don't need to do this // above in -[tableView:cellForRowAtIndexPath] because it happens // automatically when the cell is used in the table view. Also note, // the final width of the cell may not be the width of the table view in // some cases, for example when a section index is displayed along // the right side of the table view. You must account for the reduced // cell width. cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds)); // Do the layout pass on the cell, which will calculate the frames for // all the views based on the constraints. (Note that you must set the // preferredMaxLayoutWidth on multi-line UILabels inside the // -[layoutSubviews] method of the UITableViewCell subclass, or do it // manually at this point before the below 2 lines!) [cell setNeedsLayout]; [cell layoutIfNeeded]; // Get the actual height required for the cell's contentView CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; // Add an extra point to the height to account for the cell separator, // which is added between the bottom of the cell's contentView and the // bottom of the table view cell. height += 1.0; return height; } // NOTE: Set the table view's estimatedRowHeight property instead of // implementing the below method, UNLESS you have extreme variability in // your row heights and you notice the scroll indicator "jumping" // as you scroll. - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { // Do the minimal calculations required to be able to return an // estimated row height that's within an order of magnitude of the // actual height. For example: if ([self isTallCellAtIndexPath:indexPath]) { return 350.0; } else { return 40.0; } } 

Proyectos de muestra

  • Proyecto de ejemplo iOS 8 : Requiere iOS 8
  • Proyecto de ejemplo iOS 7 : funciona en iOS 7+

Estos proyectos son ejemplos completamente funcionales de vistas de tabla con alturas de fila variables debido a celdas de vista de tabla que contienen contenido dynamic en UILabels.

Xamarin (C # /. NET)

Si está utilizando Xamarin, consulte este proyecto de muestra creado por @KentBoogaart .

Para IOS8 es realmente simple:

 override func viewDidLoad() { super.viewDidLoad() self.tableView.estimatedRowHeight = 80 self.tableView.rowHeight = UITableViewAutomaticDimension } 

O

 func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension } 

Pero para IOS7, la clave es calcular la altura después del diseño automático,

 func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat { cell.setNeedsLayout() cell.layoutIfNeeded() let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0 return height } 

Importante

  • Si hay varias líneas de tags, no olvide establecer numberOfLines en 0 .

  • No te olvides de label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

El código de ejemplo completo está aquí .

Ejemplo rápido de altura variable UITableViewCell

Actualizado para Swift 3

La respuesta Swift de William Hu es buena, pero me ayuda a tener algunos pasos simples pero detallados cuando aprendes a hacer algo por primera vez. El siguiente ejemplo es mi proyecto de prueba mientras aprende a hacer una UITableView con alturas de celda variables. Lo basé en este ejemplo básico de UITableView para Swift .

El proyecto final debe verse así:

enter image description here

Crea un nuevo proyecto

Puede ser solo una aplicación de vista única.

Agrega el código

Agregue un nuevo archivo Swift a su proyecto. Nómbrelo MyCustomCell. Esta clase contendrá los puntos de venta de las vistas que agregue a su celda en el guión gráfico. En este ejemplo básico, solo tendremos una etiqueta en cada celda.

 import UIKit class MyCustomCell: UITableViewCell { @IBOutlet weak var myCellLabel: UILabel! } 

Conectaremos esta salida más tarde.

Abra ViewController.swift y asegúrese de tener el siguiente contenido:

 import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // These strings will be the data for the table view cells let animals: [String] = [ "Ten horses: horse horse horse horse horse horse horse horse horse horse ", "Three cows: cow, cow, cow", "One camel: camel", "Ninety-nine sheep: sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep", "Thirty goats: goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "] // Don't forget to enter this in IB also let cellReuseIdentifier = "cell" @IBOutlet var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() // delegate and data source tableView.delegate = self tableView.dataSource = self // Along with auto layout, these are the keys for enabling variable cell height tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension } // number of rows in table view func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.animals.count } // create a cell for each table view row func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell cell.myCellLabel.text = self.animals[indexPath.row] return cell } // method to run when table view cell is tapped func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print("You tapped cell number \(indexPath.row).") } } 

Nota IMPORTANTE:

  • Son las siguientes dos líneas de código (junto con el diseño automático) que hacen posible la altura de celda variable:

     tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension 

Configura el guión gráfico

Agregue una vista de tabla a su controlador de vista y use el diseño automático para fijarlo en los cuatro lados. A continuación, arrastre una celda de vista de tabla a la vista de tabla. Y en la celda Prototipo, arrastre una etiqueta. Utilice el diseño automático para fijar la etiqueta a los cuatro bordes de la vista de contenido de la celda de vista de tabla.

enter image description here

Nota IMPORTANTE:

  • El diseño automático funciona junto con las dos líneas importantes de código que mencioné anteriormente. Si no usa el diseño automático, no va a funcionar.

Otros ajustes de IB

Nombre de clase personalizado e identificador

Seleccione la celda de vista de tabla y configure la clase personalizada como MyCustomCell (el nombre de la clase en el archivo Swift que agregamos). cellReuseIdentifier también el identificador para que sea la cell (la misma cadena que usamos para el cellReuseIdentifier de cellReuseIdentifier de cellReuseIdentifier en el código anterior).

enter image description here

Cero líneas para etiqueta

Establezca el número de líneas en 0 en su etiqueta. Esto significa multilínea y permite que la etiqueta se cambie de tamaño en función de su contenido.

enter image description here

Enganche los sockets

  • Controle la resistencia desde Table View en el guión gráfico a la variable tableView en el código ViewController .
  • Haga lo mismo con la etiqueta en su celda Prototipo a la variable myCellLabel en la clase MyCustomCell .

Terminado

Debería poder ejecutar su proyecto ahora y obtener celdas con alturas variables.

Notas

  • Este ejemplo solo funciona para iOS 8 y posteriores. Si todavía necesita soportar iOS 7, esto no funcionará para usted.
  • Sus propias celdas personalizadas en sus proyectos futuros probablemente tendrán más de una etiqueta. Asegúrate de tener todo anclado correctamente para que el diseño automático pueda determinar la altura correcta para usar. También puede que tenga que usar resistencia de compresión vertical y aarms. Vea este artículo para más sobre eso.
  • Si no está fijando los bordes anterior y posterior (izquierdo y derecho), también puede necesitar establecer el MaxLayoutWidth preferredMaxLayoutWidth la etiqueta para que sepa cuándo ajustar la línea. Por ejemplo, si ha agregado una restricción tableView:cellForRowAtIndexPath horizontalmente a la etiqueta en el proyecto anterior en lugar de fijar los bordes tableView:cellForRowAtIndexPath y posterior, deberá agregar esta línea al método tableView:cellForRowAtIndexPath :

      cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width 

Ver también

  • Descripción de las celdas de tamaño automático y el tipo dynamic en iOS 8
  • Celdas de vista de tabla con alturas de fila variables
  • Ejemplo UITableView para Swift

Envolví la solución de iOS 7 de smileyborg en una categoría

Decidí incluir esta solución inteligente de @smileyborg en una categoría UICollectionViewCell+AutoLayoutDynamicHeightCalculation .

La categoría también rectifica los problemas descritos en la respuesta de @ wildmonkey (cargando una celda desde una punta y systemLayoutSizeFittingSize: devolviendo CGRectZero )

No tiene en cuenta el almacenamiento en caché, pero se adapta a mis necesidades en este momento. Siéntase libre de copiar, pegar y piratear.

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

 #import  typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void); /** * A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints. * * Many thanks to @smileyborg and @wildmonkey * * @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights */ @interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation) /** * Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view. * * @param name Name of the nib file. * * @return collection view cell for using to calculate content based height */ + (instancetype)heightCalculationCellFromNibWithName:(NSString *)name; /** * Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass * * @param block Render the model data to your UI elements in this block * * @return Calculated constraint derived height */ - (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width; /** * Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds */ - (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block; @end 

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

 #import "UICollectionViewCell+AutoLayout.h" @implementation UICollectionViewCell (AutoLayout) #pragma mark Dummy Cell Generator + (instancetype)heightCalculationCellFromNibWithName:(NSString *)name { UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject]; [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView]; return heightCalculationCell; } #pragma mark Moving Constraints - (void)moveInterfaceBuilderLayoutConstraintsToContentView { [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) { [self removeConstraint:constraint]; id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem; id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem; [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem attribute:constraint.firstAttribute relatedBy:constraint.relation toItem:secondItem attribute:constraint.secondAttribute multiplier:constraint.multiplier constant:constraint.constant]]; }]; } #pragma mark Height - (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block { return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])]; } - (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width { NSParameterAssert(block); block(); [self setNeedsUpdateConstraints]; [self updateConstraintsIfNeeded]; self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds)); [self setNeedsLayout]; [self layoutIfNeeded]; CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; return calculatedSize.height; } @end 

Ejemplo de uso:

 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])]; CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{ [(id)cell renderWithModel:someModel]; }]; return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height); } 

Afortunadamente no tendremos que hacer este jazz en iOS8, ¡pero ahí está por ahora!

Aquí está mi solución. Debe indicar a TableView la HeightHeight antes de cargar la vista. De lo contrario, no podrá comportarse como se espera.

 - (void)viewWillAppear:(BOOL)animated { _messageField.delegate = self; _tableView.estimatedRowHeight = 65.0; _tableView.rowHeight = UITableViewAutomaticDimension; } 

The solution proposed by @smileyborg is almost perfect. If you have a custom cell and you want one or more UILabel with dynamic heights then the systemLayoutSizeFittingSize method combined with AutoLayout enabled returns a CGSizeZero unless you move all your cell constraints from the cell to its contentView (as suggested by @TomSwift here How to resize superview to fit all subviews with autolayout? ).

To do so you need to insert the following code in your custom UITableViewCell implementation (thanks to @Adrian).

 - (void)awakeFromNib{ [super awakeFromNib]; for (NSLayoutConstraint *cellConstraint in self.constraints) { [self removeConstraint:cellConstraint]; id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem; id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem; NSLayoutConstraint *contentViewConstraint = [NSLayoutConstraint constraintWithItem:firstItem attribute:cellConstraint.firstAttribute relatedBy:cellConstraint.relation toItem:seccondItem attribute:cellConstraint.secondAttribute multiplier:cellConstraint.multiplier constant:cellConstraint.constant]; [self.contentView addConstraint:contentViewConstraint]; } } 

Mixing @smileyborg answer with this should works.

An important enough gotcha I just ran into to post as an answer.

@smileyborg’s answer is mostly correct. However, if you have any code in the layoutSubviews method of your custom cell class, for instance setting the preferredMaxLayoutWidth , then it won’t be run with this code:

 [cell.contentView setNeedsLayout]; [cell.contentView layoutIfNeeded]; 

It confounded me for awhile. Then I realized it’s because those are only triggering layoutSubviews on the contentView , not the cell itself.

My working code looks like this:

 TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"]; [cell configureWithThirdPartyObject:self.app]; [cell layoutIfNeeded]; CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; return height; 

Note that if you are creating a new cell, I’m pretty sure you don’t need to call setNeedsLayout as it should already be set. In cases where you save a reference to a cell, you should probably call it. Either way it shouldn’t hurt anything.

Another tip if you are using cell subclasses where you are setting things like preferredMaxLayoutWidth . As @smileyborg mentions, “your table view cell hasn’t yet had its width fixed to the table view’s width”. This is true, and trouble if you are doing your work in your subclass and not in the view controller. However you can simply set the cell frame at this point using the table width:

For instance in the calculation for height:

 self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"]; CGRect oldFrame = self.summaryCell.frame; self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height); 

(I happen to cache this particular cell for re-use, but that’s irrelevant).

In case people are still having trouble with this. I wrote a quick blog post about using Autolayout with UITableViews Leveraging Autolayout For Dynamic Cell Heights as well as an open source component to help make this more abstract and easier to implement. https://github.com/Raizlabs/RZCellSizeManager

(for Xcode 8.x / Xcode 9.x read at the bottom)

Beware of the following issue in in Xcode 7.x, which might be a source of confusion:

Interface Builder does not handle auto-sizing cell set-up properly. Even if your constraints are absolutely valid, IB will still complain and give you confusing suggestions and errors. The reason is that IB is unwilling to change the row’s height as your constraints dictate (so that the cell fits around your content). Instead, it keeps the row’s height fixed and starts suggesting you change your constraints, which you should ignore .

For example, imagine you’ve set up everything fine, no warnings, no errors, all works.

enter image description here

Now if you change the font size (in this example I’m changing the description label font size from 17.0 to 18.0).

enter image description here

Because the font size increased, the label now wants to occupy 3 rows (before that it was occupying 2 rows).

If Interface Builder worked as expected, it would resize the cell’s height to accommodate the new label height. However what actually happens is that IB displays the red auto-layout error icon and suggest that you modify hugging/compression priorities.

enter image description here

You should ignore these warnings. What you can* do instead is to manually change the row’s height in (select Cell > Size Inspector > Row Height).

enter image description here

I was changing this height one click at a time (using the up/down stepper) until the red arrow errors disappear! (you will actually get yellow warnings, at which point just go ahead and do ‘update frames’, it should all work).

* Note that you don’t actually have to resolve these red errors or yellow warnings in Interface Builder – at runtime, everything will work correctly (even if IB shows errors/warnings). Just make sure that at runtime in the console log you’re not getting any AutoLayout errors.

In fact trying to always update row height in IB is super annoying and sometimes close to impossible (because of fractional values).

To prevent the annoying IB warnings/errors, you can select the views involved and in Size Inspector for the property Ambiguity choose Verify Position Only

enter image description here


Xcode 8.x / Xcode 9.x seems to (sometimes) be doing things differently than Xcode 7.x, but still incorrectly. For example even when compression resistance priority / hugging priority are set to required (1000), Interface Builder might stretch or clip a label to fit the cell (instead of resizing cell height to fit around the label). And in such a case it might not even show any AutoLayout warnings or errors. Or sometimes it does exactly what Xcode 7.x did, described above.

As long as your layout in your cell is good.

 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; } 

Update: You should use dynamic resizing introduced in iOS 8.

Like @Bob-Spryn I ran into an important enough gotcha that I’m posting this as an answer.

I struggled with @smileyborg’s answer for a while. The gotcha that I ran into is if you’ve defined your prototype cell in IB with additional elements ( UILabels , UIButtons , etc.) in IB when you instantiate the cell with [ [YourTableViewCellClass alloc] init] it will not instantiate all the other elements within that cell unless you’ve written code to do that. (I had a similar experience with initWithStyle .)

To have the storyboard instantiate all the additional elements obtain your cell with [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (Not [tableView dequeueReusableCellWithIdentifier:forIndexPath:] as this’ll cause interesting problems.) When you do this all the elements you defined in IB will be instantiated.

Dynamic Table View Cell Height and Auto Layout

A good way to solve the problem with storyboard Auto Layout:

 - (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath { static RWImageCell *sizingCell = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier]; }); [sizingCell setNeedsLayout]; [sizingCell layoutIfNeeded]; CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; return size.height; } 

Another “solution”: skip all this frustration and use a UIScrollView instead to get a result that looks and feels identical to UITableView.

That was the painful “solution” for me, after having put in literally 20+ very frustrating hours total trying to build something like what smileyborg suggested and failing over many months and three versions of App Store releases.

My take is that if you really need iOS 7 support (for us, it’s essential) then the technology is just too brittle and you’ll pull your hair out trying. And that UITableView is complete overkill generally unless you’re using some of the advanced row editing features and/or really need to support 1000+ “rows” (in our app, it’s realistically never more than 20 rows).

The added bonus is that the code gets insanely simple versus all the delegate crap and back and forth that comes with UITableView. It’s just one single loop of code in viewOnLoad that looks elegant and is easy to manage.

Here’s some tips on how to do it:

1) Using either Storyboard or a nib file, create a ViewController and associated root view.

2) Drag over a UIScrollView onto your root view.

3) Add constraints top, bottom, left and right constraints to the top-level view so the UIScrollView fills the entire root view.

4) Add a UIView inside the UIScrollView and call it “container”. Add top, bottom, left and right constraints to the UIScrollView (its parent). KEY TRICK: Also add a “Equal widths” constraints to link the UIScrollView and UIView.

You will get an error “scroll view has ambiguous scrollable content height” and that your container UIView should have a height of 0 pixels. Neither error seems to matter when the app is running.

5) Create nib files and controllers for each of your “cells”. Use UIView not UITableViewCell.

5) In your root ViewController, you essentially add all the “rows” to the container UIView and programmatically add constraints linking their left and right edges to the container view, their top edges to either the container view top (for the first item) or the previous cell. Then link the final cell to the container bottom.

For us, each “row” is in a nib file. So the code looks something like this:

 class YourRootViewController { @IBOutlet var container: UIView! //container mentioned in step 4 override func viewDidLoad() { super.viewDidLoad() var lastView: UIView? for data in yourDataSource { var cell = YourCellController(nibName: "YourCellNibName", bundle: nil) UITools.addViewToTop(container, child: cell.view, sibling: lastView) lastView = cell.view //Insert code here to populate your cell } if(lastView != nil) { container.addConstraint(NSLayoutConstraint( item: lastView!, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: container, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)) } ///Add a refresh control, if you want - it seems to work fine in our app: var refreshControl = UIRefreshControl() container.addSubview(refreshControl!) } } 

And here’s the code for UITools.addViewToTop:

 class UITools { ///Add child to container, full width of the container and directly under sibling (or container if sibling nil): class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil) { child.setTranslatesAutoresizingMaskIntoConstraints(false) container.addSubview(child) //Set left and right constraints so fills full horz width: container.addConstraint(NSLayoutConstraint( item: child, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: container, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0)) container.addConstraint(NSLayoutConstraint( item: child, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: container, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0)) //Set vertical position from last item (or for first, from the superview): container.addConstraint(NSLayoutConstraint( item: child, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: sibling == nil ? container : sibling, attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)) } } 

The only “gotcha” I’ve found with this approach so far is that UITableView has a nice feature of “floating” section headers at the top of the view as you scroll. The above solution won’t do that unless you add more programming but for our particular case this feature wasn’t 100% essential and nobody noticed when it went away.

If you want dividers between your cells, just add a 1 pixel high UIView at the bottom of your custom “cell” that looks like a divider.

Be sure to turn on “bounces” and “bounce vertically” for the refresh control to work and so it seems more like a tableview.

TableView shows some empty rows and dividers under your content, if it doesn’t fill the full screen where as this solution doesn’t. But personally, I prefer if those empty rows weren’t there anyway – with variable cell height it always looked “buggy” to me anyway to have the empty rows in there.

Here’s hoping some other programmer reads my post BEFORE wasting 20+ hours trying to figure it out with Table View in their own app. 🙂

To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.

  • Assign and implement tableview dataSource and delegate
  • Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
  • Implement delegate/dataSource methods (ie heightForRowAt and return a value UITableViewAutomaticDimension to it)

C objective:

 // in ViewController.h #import  @interface ViewController : UIViewController  @property IBOutlet UITableView * table; @end // in ViewController.m - (void)viewDidLoad { [super viewDidLoad]; self.table.dataSource = self; self.table.delegate = self; self.table.rowHeight = UITableViewAutomaticDimension; self.table.estimatedRowHeight = UITableViewAutomaticDimension; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewAutomaticDimension; } 

Rápido:

 @IBOutlet weak var table: UITableView! override func viewDidLoad() { super.viewDidLoad() // Don't forget to set dataSource and delegate for table table.dataSource = self table.delegate = self // Set automatic dimensions for row height // Swift 4.2 onwards table.rowHeight = UITableView.automaticDimension table.estimatedRowHeight = UITableView.automaticDimension // Swift 4.1 and below table.rowHeight = UITableViewAutomaticDimension table.estimatedRowHeight = UITableViewAutomaticDimension } // UITableViewAutomaticDimension calculates height of label contents/text func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { // Swift 4.2 onwards return UITableView.automaticDimension // Swift 4.1 and below return UITableViewAutomaticDimension } 

For label instance in UITableviewCell

  • Set number of lines = 0 (& line break mode = truncate tail)
  • Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
  • Optional : Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

enter image description here

Note : If you’ve more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust ‘Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.

 tableView.estimatedRowHeight = 343.0 tableView.rowHeight = UITableViewAutomaticDimension 

enter image description here

I had to use dynamic views (setup views and constraints by code) and when I wanted to set preferredMaxLayoutWidth label’s width was 0. So I’ve got wrong cell height.

Then I added

 [cell layoutSubviews]; 

before executing

 [cell setNeedsUpdateConstraints]; [cell updateConstraintsIfNeeded]; 

After that label’s width was as expected and dynamic height was calculating right.

Let’s say you have a cell with a subview, and you want the cell’s height to be high enough to encompass the subview + padding.

1) Set the subview’s bottom constraint equal to the cell.contentView minus the padding you want. Do not set constraints on the cell or cell.contentView itself.

2) Set either the tableView’s rowHeight property or tableView:heightForRowAtIndexPath: to UITableViewAutomaticDimension .

3) Set either the tableView’s estimatedRowHeight property or tableView:estimatedHeightForRowAtIndexPath: to a best guess of the height.

Eso es.

If you do you layout programmatically, here is what to consider for iOS 10 using anchors in Swift.

There are three rules/ steps

NUMBER 1: set this two properties of tableview on viewDidLoad, the first one is telling to the tableview that should expect dynamic sizes on their cells, the second one is just to let the app calculate the size of the scrollbar indicator, so it helps for performance.

  tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 100 

NUMBER 2: This is important you need to add the subviews to the contentView of the cell not to the view, and also use its layoutsmarginguide to anchor the subviews to the top and bottom, this is a working example of how to do it.

 override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setUpViews() } private func setUpViews() { contentView.addSubview(movieImageView) contentView.addSubview(descriptionLabel) let marginGuide = contentView.layoutMarginsGuide NSLayoutConstraint.activate([ movieImageView.heightAnchor.constraint(equalToConstant: 80), movieImageView.widthAnchor.constraint(equalToConstant: 80), movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor), movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20), descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15), descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor), descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15), descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor) ]) } 

Create a method that will add the subviews and perform the layout, call it in the init method.

NUMBER 3: DON’T CALL THE METHOD:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { } 

If you do it you will override your implementation.

Follow this 3 rules for dynamic cells in tableviews.

here is a working implementation https://github.com/jamesrochabrun/MinimalViewController

In my case i have to create a custom cell with a image which is coming from server and can be of any width and height. And two UILabels with dynamic size(both width & height)

i have achieved the same here in my answer with autolayout and programmatically:

Basically above @smileyBorg answer helped but systemLayoutSizeFittingSize never worked for me, In my approach :

1. No use of automatic row height calculation property. 2.No use of estimated height 3.No need of unnecessary updateConstraints. 4.No use of Automatic Preferred Max Layout Width. 5. No use of systemLayoutSizeFittingSize (should have use but not working for me, i dont know what it is doing internally), but instead my method -(float)getViewHeight working and i know what it’s doing internally.

Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell?

In my case, the padding was because of the sectionHeader and sectionFooter heights, where storyboard allowed me to change it to minimum 1. So in viewDidLoad method:

 tableView.sectionHeaderHeight = 0 tableView.sectionFooterHeight = 0 

I just did some dumb try and error with the 2 values of rowHeight and estimatedRowHeight and just thought it might provide some debugging insight:

If you set them both OR only set the estimatedRowHeight you will get the desired behavior:

 tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1 

It’s suggested that you do your best to get the correct estimate, but the end result isn’t different. It will just affect your performance.

enter image description here


If you only set the rowHeight ie only do:

 tableView.rowHeight = UITableViewAutomaticDimension 

your end result would not be as desired:

enter image description here


If you set the estimatedRowHeight to 1 or smaller then you will crash regardless of the rowHeight .

 tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 1 

I crashed with the following error message:

 Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'table view row height must not be negative - provided height for index path ( {length = 2, path = 0 - 0}) is -1.000000' ...some other lines... libc++abi.dylib: terminating with uncaught exception of type NSException 

With regard to the accepted answer by @smileyborg, I have found

 [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] 

to be unreliable in some cases where constraints are ambiguous. Better to force the layout engine to calculate the height in one direction, by using the helper category on UIView below:

 -(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{ [self setNeedsLayout]; [self layoutIfNeeded]; CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; CGFloat h = size.height; return h; } 

Where w: is the width of the tableview

Simply add these two functions in your viewcontroller it will solve your problem. Here, list is a string array which contain your string of every row.

  func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row]) return (tableView.rowHeight) } func calculateHeight(inString:String) -> CGFloat { let messageString = input.text let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)] let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes) let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil) let requredSize:CGRect = rect return requredSize.height } 

yet another iOs7+iOs8 solution in Swift

 var cell2height:CGFloat=44 override func viewDidLoad() { super.viewDidLoad() theTable.rowHeight = UITableViewAutomaticDimension theTable.estimatedRowHeight = 44.0; } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell cell2height=cell.contentView.height return cell } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { if #available(iOS 8.0, *) { return UITableViewAutomaticDimension } else { return cell2height } }