ios - uitableviewcell - uitableview automatic height swift 3




Uso del diseño automático en UITableView para diseños de celdas dinámicos y alturas de fila variables (17)

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


Ejemplo veloz de una altura variable UITableViewCell

Actualizado para Swift 3

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

El proyecto terminado debería verse así:

Crear un nuevo proyecto

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

Agrega el codigo

Agrega un nuevo archivo Swift a tu 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!
}

Vamos a conectar 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:

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

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Configurar el guión gráfico

Agregue una vista de tabla a su controlador de vista y use el diseño automático para anclarlo en los cuatro lados. Luego 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 en los cuatro bordes de la vista de contenido de la celda de vista de tabla.

Nota IMPORTANTE:

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

Otras configuraciones de IB

Nombre de clase personalizado e identificador

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

Líneas cero para etiqueta

Establezca el número de líneas a 0 en su etiqueta. Esto significa multilínea y permite que la etiqueta se redimensione a sí misma en función de su contenido.

Conecte los enchufes

  • Controle el arrastre desde la vista de tabla en el guión gráfico a la variable tableView en el código de ViewController .
  • Haga lo mismo para la etiqueta en su celda de Prototipo para 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 aún necesita ser compatible con iOS 7, esto no funcionará para usted.
  • Sus propias celdas personalizadas en sus proyectos futuros probablemente tendrán más de una sola etiqueta. Asegúrese de que todo esté bien fijado para que el diseño automático pueda determinar la altura correcta que se debe usar. También es posible que tenga que usar la compresión vertical y los abrazos. Vea este artículo para más sobre eso.
  • Si no está fijando los bordes anterior y posterior (izquierdo y derecho), es posible que también tenga que configurar el MaxAmayor preferredMaxLayoutWidth de banda preferredMaxLayoutWidth para que sepa cuándo alinear. 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 finales, entonces deberá agregar esta línea al método tableView:cellForRowAtIndexPath :

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Ver también


Envolví la solución iOS7 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 (cargar una celda desde una punta y systemLayoutSizeFittingSize: devolver CGRectZero )

No tiene en cuenta ningún almacenamiento en caché, pero se adapta a mis necesidades en este momento. Siéntase libre de copiarlo, pegarlo y piratearlo.

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see .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<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

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


Para IOS8 es muy 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 autolayout,

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

Importante

  • Si hay etiquetas de varias líneas, no olvide establecer el número de numberOfLines en 0 .

  • No olvide label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

El código de ejemplo completo está here .

EDIT Swift 4.2 UITableViewAutomaticDimension cambiado a UITableView.automaticDimension


(para Xcode 8.x / Xcode 9.x leído en la parte inferior)

Tenga cuidado con el siguiente problema en Xcode 7.x, que podría ser una fuente de confusión:

Interface Builder no maneja correctamente la configuración de celdas de tamaño automático. Incluso si sus restricciones son absolutamente válidas, IB seguirá quejándose y le dará sugerencias y errores confusos. La razón es que IB no está dispuesto a cambiar la altura de la fila según lo dicten sus restricciones (para que la celda se ajuste a su contenido). En su lugar, mantiene la altura de la fila fija y comienza a sugerirle que cambie sus restricciones, que debe ignorar .

Por ejemplo, imagina que has configurado todo bien, sin advertencias, sin errores, todo funciona.

Ahora, si cambia el tamaño de fuente (en este ejemplo, estoy cambiando el tamaño de fuente de la etiqueta de la descripción de 17.0 a 18.0).

Debido a que el tamaño de la fuente aumentó, la etiqueta ahora quiere ocupar 3 filas (antes de eso ocupaba 2 filas).

Si Interface Builder funcionara como se esperaba, cambiaría el tamaño de la altura de la celda para adaptarse a la nueva altura de la etiqueta. Sin embargo, lo que realmente sucede es que IB muestra el ícono rojo de error de diseño automático y sugiere que modifique las prioridades de compresión / compresión.

Debes ignorar estas advertencias. Lo que puede * hacer en cambio es cambiar manualmente el alto de la fila en (seleccione Celda> Inspector de tamaño> Alto de fila).

Estaba cambiando esta altura un clic a la vez (utilizando el paso arriba / abajo) hasta que desaparecen los errores de la flecha roja. (En realidad recibirá advertencias amarillas, momento en el que simplemente siga adelante y haga 'actualizar marcos', todo debería funcionar).

* Tenga en cuenta que realmente no tiene que resolver estos errores rojos o advertencias amarillas en Interface Builder: en el tiempo de ejecución, todo funcionará correctamente (incluso si IB muestra errores / advertencias). Solo asegúrese de que en el tiempo de ejecución en el registro de la consola no reciba ningún error de AutoLayout.

De hecho, intentar actualizar siempre la altura de la fila en IB es súper molesto y, a veces, casi imposible (debido a los valores fraccionarios).

Para evitar las molestas advertencias / errores de IB, puede seleccionar las vistas involucradas y, Size Inspectorpara la propiedad, AmbiguityelegirVerify Position Only

Xcode 8.x / Xcode 9.x parece (a veces) estar haciendo las cosas de manera diferente a Xcode 7.x, pero sigue siendo incorrecto. Por ejemplo, incluso cuando compression resistance priority/ hugging priorityestán configurados en requerido (1000), Interface Builder puede estirar o recortar una etiqueta para que se ajuste a la celda (en lugar de cambiar el tamaño de la altura de la celda para que se ajuste a la etiqueta). Y en tal caso, es posible que ni siquiera muestre advertencias ni errores de AutoLayout. O a veces hace exactamente lo que hizo Xcode 7.x, descrito anteriormente.


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

Descripción conceptual

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

1. Configurar y agregar restricciones

En su subclase UITableViewCell , agregue restricciones para que las subvistas de la celda tengan sus bordes anclados en los bordes del contentView de la celda (lo más importante en los bordes superior e inferior). NOTA: no pegue subvistas a la celda en sí; solo para el contenido de la celda! Permita que el tamaño del contenido intrínseco de estas subvistas aumente la altura de la vista de contenido de la celda de la tabla asegurándose de que la resistencia a la compresión del contenido y las restricciones de contenido en la dimensión vertical de cada subvista no sean anuladas por las restricciones de mayor prioridad que haya agregado. ( ¿Eh? Haga 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 expandir la vista de contenido para que se ajuste a ellas. Usando una celda de ejemplo con algunas subvistas, aquí hay una ilustración visual de cómo deberían ser algunas de sus restricciones (¡no todas!) :

Puede imaginar que a medida que se agregue más texto a la etiqueta de cuerpo de varias líneas en la celda de ejemplo anterior, deberá crecer verticalmente para ajustarse al texto, lo que obligará a la celda a crecer en altura. (¡Por supuesto, debe cumplir con las restricciones para que esto funcione correctamente!)

Definir sus limitaciones es definitivamente la parte más difícil y más importante de lograr que las alturas de las celdas dinámicas trabajen con Auto Layout. Si comete un error aquí, podría impedir 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 están agregando dónde, y es mucho más fácil de depurar cuando las cosas salen mal. Agregar restricciones en el código puede ser tan fácil y significativamente más poderoso que el 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 puede llamarse más de una vez, por lo tanto, para evitar agregar las mismas restricciones más de una, asegúrese de envolver su código de adición de restricciones dentro de 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 restricciones una vez). Por otro lado, si tiene un código que actualiza las restricciones existentes (como ajustar la propiedad constant en algunas restricciones), coloque esto en updateConstraints pero fuera de la comprobación de didSetupConstraints para que pueda ejecutarse cada vez que se llame al método.

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

Para cada conjunto único de restricciones en la celda, use un identificador único de reutilización de celda. En otras palabras, si sus celdas tienen más de un diseño único, cada diseño único debe recibir su propio identificador de reutilización. (Un buen indicio de que necesita usar un nuevo identificador de reutilización es cuando su variante de celda tenga un número diferente de subvistas, o las subvistas estén organizadas de una manera distinta)

Por ejemplo, si estaba mostrando un mensaje de correo electrónico en cada celda, podría tener 4 diseños únicos: mensajes con solo un asunto, mensajes con un asunto y un cuerpo, mensajes con un asunto y un archivo adjunto con foto, y mensajes con un tema, Cuerpo, y foto adjunta. Cada diseño tiene restricciones completamente diferentes requeridas para lograrlo, así que una vez que la celda se inicializa y se agregan las restricciones para uno de estos tipos de celdas, la celda debe obtener un identificador de reutilización único específico para ese tipo de celda. Esto significa que cuando saca una celda para su reutilización, las restricciones ya se han agregado y están listas para 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) pueden tener alturas variables. No confunda diseños fundamentalmente diferentes (diferentes restricciones) con diferentes marcos de vista calculados (resueltos a partir de restricciones idénticas) debido a los diferentes tamaños de contenido.

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

Para iOS 8 - Celdas de auto-dimensionamiento

3. Habilitar estimación de altura de fila

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

Apple: Trabajar con celdas de vista de tabla de auto-dimensionamiento

Con iOS 8, Apple ha internalizado gran parte del trabajo que antes tenía que implementar antes de iOS 8. Para permitir que funcione el mecanismo de celda de auto-dimensionamiento, primero debe configurar la propiedad rowHeight en la vista de tabla en el constante UITableViewAutomaticDimension . Entonces, simplemente necesita habilitar la estimación del alto de fila al establecer la propiedad estimaRowHeight de 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 esto hace es proporcionar a la vista de tabla un estimado / marcador de posición temporal para las alturas de fila de celdas que aún no están en pantalla. Luego, cuando estas celdas estén a punto de desplazarse en 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 necesita su contentView para basarse en el ancho fijo conocido de la vista de contenido (que se basa en el ancho de la vista de tabla, menos cualquier cosa adicional como una sección). vista de índice o accesorio) y las restricciones de diseño automático que ha agregado a la vista de contenido y subvistas 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 en 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 usa para dimensionar correctamente el indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo para ajustar el indicador de desplazamiento para estimaciones incorrectas a medida que desplazar celdas en pantalla. Debería establecer la propiedad viewDidLoad en la vista de tabla (en viewDidLoad o similar) a un valor constante que es el alto de fila "promedio". Solo si las alturas de sus filas tienen una variabilidad extrema (por ejemplo, difieren en un orden de magnitud) y observa que el indicador de desplazamiento "salta" a medida que se desplaza, debería molestarse en implementar tableView:estimatedHeightForRowAtIndexPath: para realizar el cálculo mínimo requerido para devolver una estimación más precisa cada fila.

Para soporte de iOS 7 (implementando el auto tamaño de la celda)

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

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

Luego, obligue a la celda a distribuir inmediatamente sus subvistas, y luego use el método UITableViewCell systemLayoutSizeFittingSize: en 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 ajustar todo el contenido de la celda. La altura se puede devolver desde el tableView:heightForRowAtIndexPath: delegate.

4. Utilice las alturas de fila estimadas

Si su vista de tabla tiene más de un par de docenas de filas, encontrará que la resolución de restricciones de Auto Layout puede atascar rápidamente el subproceso principal cuando se carga la vista de la tabla, como tableView:heightForRowAtIndexPath: se llama en cada fila en la primera carga (para calcular el tamaño del indicador de desplazamiento).

A partir de iOS 7, puede (y absolutamente debería) usar la propiedad estimatedRowHeight en la vista de tabla. Lo que esto hace es proporcionar a la vista de tabla un estimado / marcador de posición temporal para las alturas de fila de celdas que aún no están en pantalla. Luego, cuando estas celdas estén a punto de desplazarse en la pantalla, se calculará la altura real de la fila (llamando a tableView:heightForRowAtIndexPath: , y la altura estimada se actualizará con la real.

En general, la estimación que proporciona no tiene que ser muy precisa, solo se usa para dimensionar correctamente el indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo para ajustar el indicador de desplazamiento para estimaciones incorrectas a medida que desplazar celdas en pantalla. Debería establecer la propiedad viewDidLoad en la vista de tabla (en viewDidLoad o similar) a un valor constante que es el alto de fila "promedio". Solo si las alturas de sus filas tienen una variabilidad extrema (por ejemplo, difieren en un orden de magnitud) y observa que el indicador de desplazamiento "salta" a medida que se desplaza, debería molestarse en implementar tableView:estimatedHeightForRowAtIndexPath: para realizar el cálculo mínimo requerido para devolver una estimación más precisa cada fila.

5. (Si es necesario) Agregar almacenamiento de altura de fila

Si ha hecho todo lo anterior y sigue encontrando que el rendimiento es inaceptablemente lento al realizar la resolución de restricciones en tableView:heightForRowAtIndexPath: desafortunadamente, deberá implementar algo de almacenamiento en caché para las alturas de las celdas. (Este es el enfoque sugerido por los ingenieros de Apple). La idea general es dejar que el motor Auto Layout resuelva las restricciones la 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é para una celda cuando ocurra algo que pueda hacer que cambie la altura de la celda; principalmente, esto ocurre cuando cambia el contenido de esa celda o cuando ocurren otros eventos importantes (como el ajuste del usuario el control deslizante de tamaño de texto de tipo dinámico).

Código de muestra genérico de 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

Estos proyectos son ejemplos completos de vistas de tabla con alturas de fila variables debido a celdas de vista de tabla que contienen contenido dinámico en UILabels.

Xamarin (C # / .NET)

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


Con respecto a la respuesta aceptada por @smileyborg, he encontrado

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

ser poco fiable en algunos casos donde las restricciones son ambiguas. Es mejor forzar el motor de diseño para calcular la altura en una dirección, utilizando la categoría de ayudante en UIView a continuación:

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

Donde w: es el ancho de la vista de tabla


En mi caso, el relleno se debió a las alturas de Header y SectionFooter, donde el guión gráfico me permitió cambiarlo al mínimo 1. En el método viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

Mientras su diseño en su celda sea bueno.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

Actualización: debe usar el cambio de tamaño dinámico introducido en iOS 8.


Para configurar la dimensión automática para el alto de la fila y el alto estimado de la fila, asegúrese de seguir los siguientes pasos para que la dimensión automática sea efectiva para el diseño del alto de celda / fila.

  • Asignar e implementar tableview dataSource y delegar
  • Asignar UITableViewAutomaticDimensiona rowHeight y verifiedRowHeight
  • Implementar métodos de delegado / dataSource (es decir, heightForRowAty devolverle un valor UITableViewAutomaticDimension)

-

C objetivo:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

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

Para instancia de etiqueta en UITableviewCell

  • Establecer el número de líneas = 0 (y el modo de salto de línea = truncar cola)
  • Establezca todas las restricciones (arriba, abajo, derecha, izquierda) con respecto a su contenedor de supervisión / celda.
  • Opcional : establezca la altura mínima para la etiqueta, si desea un área vertical mínima cubierta por la etiqueta, incluso si no hay datos.

Nota : Si tiene más de una etiqueta (UIElements) con longitud dinámica, que debe ajustarse de acuerdo con su tamaño de contenido: Ajuste 'Abrazar contenido y prioridad de resistencia de compresión' para etiquetas que desea expandir / comprimir con mayor prioridad.


Si realiza el diseño mediante programación, esto es lo que debe considerar para iOS 10 mediante el uso de anclas en Swift.

Hay tres reglas / pasos

NÚMERO 1: establezca estas dos propiedades de tableview en viewDidLoad, la primera le dice a la tableview que debe esperar tamaños dinámicos en sus celdas, la segunda es solo para permitir que la aplicación calcule el tamaño del indicador de la barra de desplazamiento, por lo que ayuda a actuación.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NÚMERO 2: Esto es importante, debe agregar las subvistas a la vista de contenido de la celda, no a la vista, y también usar su layoutsmarginguide para anclar las subvistas en la parte superior e inferior, este es un ejemplo práctico de cómo hacerlo.

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)

        ])
}

Cree un método que agregue las subvistas y realice el diseño, llámelo en el método init.

NÚMERO 3: NO LLAME AL MÉTODO:

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

Si lo haces, anularás tu implementación.

Siga estas 3 reglas para las celdas dinámicas en las vistas de tabla.

Aquí hay una implementación de trabajo https://github.com/jamesrochabrun/MinimalViewController


Un gotcha lo suficientemente importante que acabo de encontrar como respuesta.

La respuesta de @ smileyborg es correcta en su mayoría. Sin embargo, si tiene algún código en el layoutSubviewsmétodo de su clase de celda personalizada, por ejemplo, si configura preferredMaxLayoutWidth, no se ejecutará con este código:

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

Me confundió por un rato. Entonces me di cuenta de que es porque solo están activando layoutSubviews en contentView, no la celda en sí.

Mi código de trabajo se ve así:

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

Tenga en cuenta que si está creando una nueva celda, estoy bastante seguro de que no necesita llamar, setNeedsLayoutya que ya debería estar configurado. En los casos en que guarda una referencia a una celda, probablemente debería llamarlo. De cualquier manera no debería lastimar nada.

Otro consejo si está usando subclases de celdas donde está configurando cosas como preferredMaxLayoutWidth. Como menciona @smileyborg, "su celda de vista de tabla aún no tiene su ancho fijo al ancho de la vista de tabla". Esto es cierto, y es un problema si está haciendo su trabajo en su subclase y no en el controlador de vista. Sin embargo, simplemente puede establecer el marco de la celda en este punto utilizando el ancho de la tabla:

Por ejemplo, en el cálculo de la altura:

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

(Resulta que tengo que almacenar en caché esta celda particular para reutilizarla, pero eso es irrelevante)


otra solución iOs7 + iOs8 en 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
    }
}

Al igual que me topé con una lo suficientemente importante como para publicar esto como respuesta.

Luché con @smileyborg's respuesta por un tiempo. El Gotcha que me encontré es que si usted ha definido su celular prototipo en IB con elementos adicionales ( UILabels, UIButtons, etc.) en IB cuando se instancia la celda con [ [YourTableViewCellClass alloc] init]no va a crear una instancia de todos los otros elementos dentro de esa célula a menos que haya código escrito para hacer eso. (Tuve una experiencia similar con initWithStyle.)

Para que el guión gráfico ejemplifique todos los elementos adicionales, obtenga su celda con [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](No [tableView dequeueReusableCellWithIdentifier:forIndexPath:]porque esto causará problemas interesantes). Cuando haga esto, se instanciarán todos los elementos definidos en IB.



La solución propuesta por @smileyborg es casi perfecta. Si tiene una celda personalizada y desea una o más UILabelcon alturas dinámicas, entonces el método systemLayoutSizeFittingSize combinado con AutoLayout habilitado devuelve a CGSizeZeromenos que mueva todas sus restricciones de celda de la celda a su contentView (como lo sugiere @TomSwift aquí) Cómo cambiar el tamaño de la vista a encajar todas las subvistas con autolayout? ).

Para hacerlo, debe insertar el siguiente código en su implementación personalizada de UITableViewCell (gracias a @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];
    }
}

Mezclar la respuesta de @smileyborg con esto debería funcionar.


Si tienes una cadena larga. por ejemplo, uno que no tiene un salto de línea. Entonces usted podría tener algunos problemas.

La solución se menciona por la respuesta aceptada y algunas otras respuestas. Solo necesitas añadir

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Me parece la respuesta de Suragh el más completo, mientras que el menos confuso.

Aunque no explique por qué . Vamos a hacer eso.

Coloque el siguiente código en un proyecto.

import UIKit

class ViewController: UIViewController {

    lazy var label : UILabel = {
        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.backgroundColor = .red
        lbl.textColor = .black
        return lbl
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // step0: (0.0, 0.0)
        print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
        // step1: (29.0, 20.5)
        label.text = "hiiiii"
        print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
        // step2: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints"
        print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // step3: (992.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
        print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
        // step4: (328.0, 20.5)
        label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
        print("3 translate w/ line breaks intrinsicContentSize: \(label.intrinsicContentSize)") 
        // step5: (328.0, 61.0)
        label.numberOfLines = 0
        print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)") 
        // step6: (98.5, 243.5)
        label.preferredMaxLayoutWidth = 100
        print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)") 

        setupLayout()
    }
    func setupLayout(){
        view.addSubview(label)
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
}

Tenga en cuenta que no he añadido ninguna restricción de tamaño . Solo he añadido centrex, centrado en las restricciones. Pero aún así la etiqueta tendrá el tamaño correcto ¿Por qué? Debido a contentSize.

Para procesar mejor esto, primero mantenga el paso 0, luego comente los pasos 1-6. Deja que se setupLayout()quede. Observe el comportamiento.

Luego descomenta el paso 1, y observa.

Luego descomenta el paso 2 y observa.

Haga esto hasta que haya comentado los 6 pasos y observado sus comportamientos.

¿Qué podemos concluir de todo esto? ¿Qué factores pueden cambiar el contenSize?

  1. Longitud del texto: si tiene un texto más largo, el ancho de su tamaño intrínseco de contenido aumentará
  2. Saltos de línea: si agrega \n, el ancho de intrinsicContentSize será el ancho máximo de todas las líneas. Si una línea tiene 25 caracteres, otra tiene 2 caracteres y otra tiene 21 caracteres, su ancho se calculará en base a los 25 caracteres
  3. Número de líneas permitidas: debe establecer el valor numberOfLinespara que, de lo 0contrario, no tenga varias líneas. Tu numberOfLinesajustarás la altura de tu intrinsicContentSize
  4. Realización de ajustes: imagine que según su texto, el ancho de su 200tamaño de contenido intrínseco del contenido era y la altura 100, pero quería limitar el ancho al contenedor de la etiqueta, ¿qué va a hacer? La solución es configurarlo en un ancho deseado. Para ello, configúrelo preferredMaxLayoutWidthpara que 130su nuevo intrinsicContentSize tenga un ancho aproximado 130. La altura obviamente sería más que 100porque necesitarías más líneas. Dicho esto, si sus restricciones están configuradas correctamente, ¡no necesitará utilizar esto en absoluto! Para más sobre esto ver esta respuesta y sus comentarios. Solo debe usarlo preferredMaxLayoutWidthsi no tiene restricciones que limiten el ancho / alto como en uno podría decir "no ajuste el texto a menos que exceda elpreferredMaxLayoutWidth". Sin embargo, con 100% de certeza si se establece el líder / detrás y numberOfLinesa 0continuación, ya está bueno! Larga historia corta mayoría de las respuestas aquí que recomiendan el uso que se equivocan! Usted no lo necesita. Necesidad que es una señal de que sus limitaciones no están configurados correctamente o simplemente no tiene restricciones

  5. Tamaño de fuente: también tenga en cuenta que si aumenta el tamaño de fuente, entonces la altura del tamaño de elemento intrínsico aumentará. No lo mostré en mi código. Puedes intentarlo por tu cuenta.

Así que volvamos a su ejemplo de tableViewCell:

Todo lo que necesita hacer es establecer la numberOfLinesa 0y establezca el preferredMaxLayoutWidthde su tableView's width.


tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension





row-height