Uso del diseño automático en UITableView para diseños de celdas dinámicas y alturas de fila variables


¿Cómo se utiliza el Diseño automático dentro de UITableViewCells en una vista de tabla para permitir que el contenido y las subviews de cada celda determinen la altura de la fila (en sí/automáticamente), mientras se mantiene un rendimiento de desplazamiento suave?

Author: Krunal, 2013-09-11

25 answers

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

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 Añadir Restricciones

En su subclase UITableViewCell, agregue restricciones para que las subviews de la celda tiene sus bordes fijados a los bordes de la celda contentView (lo más importante a los bordes superior E inferior). NOTA: no pin subviews a la celda en sí; solo a contentView de la celda! Deje que el tamaño de contenido intrínseco de estas subviews impulse la altura de la vista de contenido de la celda de la vista de tabla asegurándose de que las restricciones resistencia a la compresión de contenido y abrazo de contenido en la dimensión vertical para cada subviews no estén siendo anuladas por restricciones de mayor prioridad que ha agregado. (Huh? Haga clic aquí.)

Recuerde, la idea es tener las subviews 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 ajustarlas. Usando una celda de ejemplo con algunas subviews, aquí hay una ilustración visual de lo que algunos (¡no todos!) de sus restricciones tendría que parecerse a:

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

Se puede imaginar que como más texto se agrega 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 obligará efectivamente a la celda a crecer en altura. (Por supuesto, es necesario obtener las restricciones correctas para que esto funcione correctamente!)

Obtener sus restricciones correctamente es definitivamente la parte más difícil y más importante de obtener alturas de celda dinámicas que funcionen con Diseño automático. Si cometes un error aquí, podría evitar que todo lo demás funcione -- así que tómate tu tiempo! Recomiendo configurar sus restricciones en código porque sabe exactamente qué restricciones se están agregando dónde, y es mucho más fácil depurar cuando las cosas salen mal. Agregar restricciones en el código puede ser tan fácil y significativamente más potente que Interface Builder usando anclajes de diseño, o una de las fantásticas API de código abierto disponibles en GitHub.

  • Si está agregando restricciones en el código, debe hacer esto una vez desde el método updateConstraints de su Subclase UITableViewCell. Tenga en cuenta que updateConstraints puede ser llamado más de una vez, así que para evitar agregar las mismas restricciones más de una vez, asegúrese de envolver su código de adición de restricción dentro de updateConstraints en una comprobación de una propiedad booleana como didSetupConstraints (que se establece en SÍ después de ejecutar su código de adición de restricción una vez). Por otro lado, si tiene código que actualiza las restricciones existentes (como ajustar la propiedad constant en algunas restricciones), colóquelo en updateConstraints pero fuera de la comprobación de didSetupConstraints para que puede ejecutarse cada vez que se llama 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 único de reutilización de celdas. 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. (Una buena pista de que necesita usar un nuevo identificador de reutilización es cuando su variante de celda tiene un número diferente de subviews, o las subviews están organizadas en una distinta moda.)

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 asunto y un cuerpo, mensajes con un asunto y un archivo adjunto de foto, y mensajes con un asunto, cuerpo y archivo adjunto de foto. 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 una reutilización única identificador específico para ese tipo de celda. Esto significa que cuando desque una celda para reutilizarla, 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) aún pueden tener alturas variables. No confunda fundamentalmente diferentes diseños (diferentes restricciones) con diferentes marcos de vista calculados (resueltos a partir de restricciones idénticas) debido a 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 dequeue. El motor de Diseño automático interno no está diseñado para manejar cambios a gran escala en restricciones, y verá problemas de rendimiento masivos.

Para iOS 8-Celdas de Tamaño automático

3. Habilitar Altura de Fila Estimación

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

Apple: Trabajando con Celdas de Vista de Tabla de Tamaño propio

Con iOS 8, Apple ha internalizado gran parte de el trabajo que previamente tenía que ser implementado por usted antes de iOS 8. Para permitir que el mecanismo de celda de tamaño automático funcione, primero debe establecer la propiedad rowHeight en la vista de tabla a la constante UITableViewAutomaticDimension. Luego, simplemente necesita habilitar la estimación de altura de fila estableciendo la propiedad estimatedRowHeight de la vista de tabla a 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 una estimación/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 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 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 un índice de sección o una vista de accesorios) y las restricciones de diseño automático que ha agregado a la vista de contenido y subviews de la celda. Una vez que esta altura de celda real se ha determinado, la altura estimada antigua 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 términos generales, la estimación que proporcione no tiene que ser muy precisa only solo se usa para dimensionar correctamente el indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo al ajustar el indicador de desplazamiento para estimaciones incorrectas mientras desplaza las celdas en pantalla. Usted debe establecer la propiedad estimatedRowHeight en la vista de tabla (en viewDidLoad o similar) a un valor constante que es 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 si se molesta en implementar tableView:estimatedHeightForRowAtIndexPath: para hacer el cálculo mínimo requerido para devolver una estimación más precisa para cada fila.

Para el soporte de iOS 7 (implementando el tamaño automático de la celda usted mismo)

3. Hacer un Pase De Diseño y Obtenga La Altura De La Celda

Primero, instanciar una instancia fuera de pantalla de una celda de vista de tabla, una instancia para cada identificador de reutilización, que se utiliza estrictamente para cálculos de altura. (Offscreen significa que la referencia de celda se almacena en una propiedad/ivar en el controlador de vista y nunca se devuelve desde tableView:cellForRowAtIndexPath: para que la vista de tabla realmente se muestre en pantalla.) A continuación, la celda debe configurarse con el contenido exacto (por ejemplo, texto, imágenes, etc.) que sostendría si fuera se muestra en la vista de tabla.

Luego, fuerce a la celda a diseñar inmediatamente sus subviews, y luego use el método systemLayoutSizeFittingSize: en los UITableViewCell's 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 ajustarse a todo el contenido de la celda. La altura puede ser devuelta desde el método delegado tableView:heightForRowAtIndexPath:.

4. Use Alturas Estimadas de Fila

Si su vista de tabla tiene más de un par de docenas de filas, encontrará que haciendo el La resolución de restricciones de diseño automático puede atascar rápidamente el hilo principal cuando se carga por primera vez la vista de tabla, ya que tableView:heightForRowAtIndexPath: se llama en todas y cada una de las filas durante la primera carga (para calcular el tamaño del indicador de desplazamiento).

A partir de iOS 7, puede (y absolutamente debe) usar la propiedad estimatedRowHeight en la vista de tabla. Lo que esto hace es proporcionar a la vista de tabla una estimación/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 pantalla, se calculará la altura real de la fila (llamando a tableView:heightForRowAtIndexPath:), y la altura estimada se actualizará con la altura real.

En términos generales, la estimación que proporcione no tiene que ser muy precisa only solo se usa para dimensionar correctamente el indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo al ajustar el indicador de desplazamiento para estimaciones incorrectas mientras desplaza las celdas en pantalla. Debe establecer la propiedad estimatedRowHeight en la vista de tabla (en viewDidLoad o similar) a un valor constante que es 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 si se molesta en implementar tableView:estimatedHeightForRowAtIndexPath: para hacer el cálculo mínimo requerido para devolver una estimación más precisa para cada fila.

5. (Si Es Necesario) Agregar Caché de Altura de Fila

Si usted ha hecho todo lo anterior y todavía está encontrando que el rendimiento es inaceptablemente lento cuando haciendo la resolución de restricciones en tableView:heightForRowAtIndexPath:, desafortunadamente necesitará implementar algo de almacenamiento en caché para alturas de celda. (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 la primera vez, luego almacene en caché la altura calculada para esa celda y use 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 de caché de una celda cuando sucede algo que podría causar la altura a cambiar primarily principalmente, esto sería cuando el contenido de esa celda cambia o cuando ocurren otros eventos importantes (como el usuario ajustando el deslizador de tamaño de texto de tipo Dinámico).

Código de ejemplo 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 de trabajo completo de vistas de tabla con alturas de fila variables debido a las celdas de vista de tabla que contienen contenido dinámico en etiquetas UIL.

Xamarin (C#/. NET)

Si estás usando Xamarin, echa un vistazo a este proyecto de ejemplo elaborado por @KentBoogaart.

 2289
Author: smileyborg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-24 05:24:23

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 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 se etiquetan varias líneas, no olvide establecer numberOfLines en 0.

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

El código de ejemplo completo es aquí.

 131
Author: William Hu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-16 13:35:18

Swift ejemplo de una altura variable UITableViewCell

Actualizado para Swift 3

La respuesta rápida de William Hu es buena, pero me ayuda 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 como esto:

introduzca la descripción de la imagen aquí

Crear un nuevo proyecto

Puede ser solo una Aplicación de Vista Única.

Añadir el código

Agregue un nuevo archivo Swift a su proyecto. Nombre MyCustomCell. Esta clase mantendrá los puntos de venta para las vistas que agregue a su celda en el guion 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 toma más tarde.

Abra ViewController.swift y asegúrese de que tiene 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) las que hacen posible la altura variable de la celda:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Configurar el storyboard

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

introduzca la descripción de la imagen aquí

Nota Importante:

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

Otros ajustes de IB

Nombre e Identificador de clase personalizado

Seleccione la celda de la vista de tabla y establezca la clase personalizada como MyCustomCell (el nombre de la clase en el archivo Swift hemos añadido). También establece que el Identificador sea cell (la misma cadena que usamos para el cellReuseIdentifier en el código anterior.

introduzca la descripción de la imagen aquí

Cero líneas para Label

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

introduzca la descripción de la imagen aquí

Conecta las salidas

  • Arrastre de control desde la vista de tabla en el guion gráfico a la tableView variable en el código ViewController.
  • Haga lo mismo para 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 admitir 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 para usar. También puede tener que usar resistencia a la compresión vertical y abrazos. Ver este artículo para más sobre eso.
  • Si no está fijando los bordes inicial y final (izquierda y derecha), también puede necesitar establecer el preferredMaxLayoutWidth de la etiqueta para que sepa cuándo ajustar la línea. Por ejemplo, si ha agregado una restricción Horizontal de Centro a la etiqueta en el proyectar arriba en lugar de fijar los bordes inicial y final, entonces tendría que agregar esta línea al método tableView:cellForRowAtIndexPath:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Véase también

 75
Author: Suragch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:39

Envolví la solución iOS7 de @smileyborg en una categoría

Decidí envolver 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 un plumín y systemLayoutSizeFittingSize: devolviendo CGRectZero)

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

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 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<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!

 61
Author: Adam Waite,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-20 17:28:24

Aquí está mi solución. Es necesario decirle a la tableView el estimatedHeight antes de que cargue la vista. De lo contrario no será capaz de comportarse como se espera.

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}
 44
Author: eddwinpaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-13 23:56:19

La solución propuesta por @smileyborg es casi perfecta. Si tiene una celda personalizada y desea una o más UILabel con alturas dinámicas, entonces el método systemLayoutSizeFittingSize combinado con Autoayout habilitado devuelve un CGSizeZero a menos que mueva todas las restricciones de celda de la celda a su contentView (como sugiere @TomSwift aquí ¿Cómo cambiar el tamaño de superview para que se ajuste a todas las subviews con autoayout?).

Para hacerlo, debe insertar el siguiente código en su personalizado Implementación 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.

 43
Author: wildmonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:39

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

La respuesta de@smileyborg es mayormente correcta. Sin embargo, si tiene algún código en el método layoutSubviews de su clase de celda personalizada, por ejemplo configurando el preferredMaxLayoutWidth, entonces no se ejecutará con este código:

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

Me confundió por un tiempo. Entonces me di cuenta de que es porque solo están activando layoutSubviews en el 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 a setNeedsLayout ya que debería estar configurada. En los casos en que guarde una referencia a una celda, probablemente debería llamarlo. De cualquier manera no debería doler nada.

Otro consejo si está utilizando subclases de celda donde está configurando cosas como preferredMaxLayoutWidth. Como menciona @smileyborg, "su celda de vista de tabla aún no ha tenido su ancho fijado al ancho de la vista de tabla". Esto es cierto, y problemas si usted está haciendo su trabajo en su subclase y no en el controlador de vista. Sin embargo, simplemente puede establecer el marco de 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);

(Sucede que cacheo esta celda en particular para su reutilización, pero eso es irrelevante).

 23
Author: Bob Spryn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-03 06:03:00

En caso de que la gente todavía tenga problemas con esto. Escribí una entrada de blog rápida sobre el uso de Autoayout con UITableViews Aprovechando Autoayout Para Alturas de Celda Dinámicas, así como un componente de código abierto para ayudar a hacer esto más abstracto y más fácil de implementar. https://github.com/Raizlabs/RZCellSizeManager

 20
Author: Alex Rouse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-05 15:53:36

(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 la configuración de celdas de tamaño automático correctamente. Incluso si sus restricciones son absolutamente válidas, IB seguirá quejándose y le dará sugerencias confusas y errores. La razón es que IB no está dispuesto a cambiar la altura de la fila a medida que sus restricciones lo dicten (para que la celda se ajuste a su contenido). En lugar de eso, mantiene la altura de la fila fija y comienza a sugerir que cambie sus restricciones, que usted debe ignorar.

Por ejemplo, imagine que ha configurado todo bien, sin advertencias, sin errores, todo funciona.

introduzca la descripción de la imagen aquí

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

introduzca la descripción de la imagen aquí

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

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

introduzca la descripción de la imagen aquí

Debe ignorar estas advertencias. Lo que puede hacer * en su lugar es cambiar manualmente la altura de la fila en (seleccione Celda > Inspector de tamaño > Fila Altura).

introduzca la descripción de la imagen aquí

Estaba cambiando esta altura un clic a la vez (usando el paso a paso arriba/abajo) hasta que los errores de flecha roja desaparecen! (en realidad, obtendrá advertencias amarillas, en cuyo punto solo tiene que seguir adelante y hacer 'actualizar marcos', todo debería funcionar).

* Tenga en cuenta que en realidad no tiene que resolver estos errores rojos o advertencias amarillas en Interface Builder: en tiempo de ejecución, todo funcionará correctamente (incluso si IB muestra errores/advertencias). Sólo asegúrese de que en tiempo de ejecución en el registro de la consola no se producen errores de diseño automático.

De hecho, tratar de 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 en Size Inspector para la propiedad Ambiguity elegir Verify Position Only

introduzca la descripción de la imagen aquí


Xcode 8.x / Xcode 9.x parece (a veces) estar haciendo las cosas de manera diferente a Xcode 7.x, pero todavía incorrectamente. Por ejemplo, incluso cuando compression resistance priority / hugging priority se establecen 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, puede que ni siquiera muestre advertencias o errores de diseño automático. O a veces hace exactamente lo que Xcode 7.x lo hizo, descrito anteriormente.

 18
Author: Nikolay Suvandzhiev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-25 08:47:55

Siempre y cuando 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.

 17
Author: Chris Van Buskirk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-12 22:11:35

Like @Bob-Spryn Me encontré con un gotcha lo suficientemente importante como para publicar esto como respuesta.

Luché con @smileyborg respuesta por un tiempo. La trampa con la que me encontré es si ha definido su celda prototipo en IB con elementos adicionales(UILabels, UIButtons, etc.) en IB cuando instancies la celda con [[YourTableViewCellClass alloc] init] no instanciará todos los demás elementos dentro de esa celda a menos que hayas escrito código para hacerlo. (Tuve una experiencia similar con initWithStyle.)

Para que el storyboard instancie todos los elementos adicionales, obtenga su celda con [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (No [tableView dequeueReusableCellWithIdentifier:forIndexPath:] ya que esto causará problemas interesantes.) Al hacer esto, se crearán instancias de todos los elementos que haya definido en IB.

 15
Author: nickb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:33

Altura de Celda de Vista de Tabla Dinámica y Diseño Automático

Una buena manera de resolver el problema con el diseño automático de storyboard:

- (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;
}
 14
Author: Mohnasm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-05 09:34:38

Para establecer la dimensión automática para la altura de la fila y la altura estimada de la fila, asegúrese de seguir los pasos para hacer que la dimensión automática sea efectiva para el diseño de altura de celda/fila.

  • Asignar e implementar fuente de datos tableview y delegar
  • Asignar UITableViewAutomaticDimension a rowHeight & estimatedRowHeight
  • Implementar métodos de delegado / fuente de datos (es decir, heightForRowAt y devolverle un valor UITableViewAutomaticDimension)

-

Objetivo C:

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

Swift:

@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 (&modo de salto de línea = truncar cola)
  • Establece todas las restricciones (arriba, abajo, derecha izquierda) con respecto a su contenedor superview/ cell.
  • 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.

introduzca la descripción de la imagen aquí

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 'Prioridad de Abrazo de Contenido y Resistencia a la Compresión` para las etiquetas que desea expandir/comprimir con mayor prioridad.

 14
Author: Krunal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-05 14:23:22

Otra "solución": omita toda esta frustración y use un UIScrollView en su lugar para obtener un resultado que se vea y se sienta idéntico a UITableView.

Esa fue la dolorosa "solución" para mí, después de haber puesto literalmente más de 20 horas muy frustrantes en total tratando de construir algo como lo que smileyborg sugirió y fallando durante muchos meses y tres versiones de las versiones de la Tienda de aplicaciones.

Mi opinión es que si realmente necesita soporte para iOS 7 (para nosotros, es esencial), entonces la tecnología es demasiado quebradizo y te arrancarás el pelo intentándolo. Y que UITableView es un exceso completo en general, a menos que esté utilizando algunas de las funciones avanzadas de edición de filas y/o realmente necesita soportar más de 1000 "filas" (en nuestra aplicación, es realista nunca más de 20 filas).

La ventaja añadida es que el código se vuelve increíblemente simple frente a toda la basura de delegado y de ida y vuelta que viene con UITableView. Es solo un bucle de código en viewOnLoad que se ve elegante y es fácil de gestionar.

Aquí hay algunos consejos sobre cómo hacerlo:

1) Usando Storyboard o un archivo nib, cree un ViewController y una vista raíz asociada.

2) Arrastre sobre UIScrollView a su vista raíz.

3) Agregue restricciones top, bottom, left and right a la vista de nivel superior para que UIScrollView llene toda la vista raíz.

4) Agregue un UIView dentro del UIScrollView y llámelo "container". Añadir restricciones superior, inferior, izquierda y derecha a UIScrollView (su padre). TRUCO CLAVE: También agregue restricciones de "Anchuras iguales" para vincular UIScrollView y UIView.

Aparecerá un error "scroll view has ambiguous scrollable content height" y que su contenedor UIView debe tener una altura de 0 píxeles. Ninguno de los errores parece importar cuando la aplicación se está ejecutando.

5) Cree archivos nib y controladores para cada una de sus "celdas". Utilice UIView no UITableViewCell.

5) En su root ViewController, esencialmente agrega todas las "filas" a la UIView del contenedor y agrega restricciones mediante programación que vinculan sus bordes izquierdo y derecho a la vista del contenedor, sus bordes superiores a la vista del contenedor superior (para el primer elemento) o a la celda anterior. Luego vincule la celda final a la parte inferior del contenedor.

Para nosotros, cada "fila" está en un archivo nib. Así que el código se ve algo como esto:

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!)
    }
}

Y aquí está el código para 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))
    }
}

El único "gotcha" que he encontrado con este enfoque hasta ahora es que UITableView tiene una buena característica de encabezados de sección" flotantes " en la parte superior de la vista mientras se desplaza. La solución anterior no hará eso a menos que agregue más programación, pero para nuestro caso particular, esta característica no era 100% esencial y nadie se dio cuenta cuando desapareció.

Si desea divisores entre sus celdas, simplemente agregue un UIView de 1 píxel de alto en la parte inferior de su "celda" personalizada que se parezca a un divisor.

Asegúrese de activar "rebotes" y "rebote verticalmente" para que el control de actualización funcione y así que parece más una vista panorámica.

TableView muestra algunas filas vacías y divisores debajo de su contenido, si no llena la pantalla completa donde esta solución no lo hace. Pero personalmente, prefiero si esas filas vacías no estaban allí de todos modos - con altura de celda variable siempre me pareció "con errores" de todos modos tener las filas vacías allí.

Aquí está la esperanza de que algún otro programador lee mi post ANTES de perder más de 20 horas tratando de averiguarlo con la vista de tabla en su propia aplicación. :)

 12
Author: alpsystems.com,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-14 22:17:20
tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension

introduzca la descripción de la imagen aquí

 10
Author: Abo3atef,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-30 12:43:50

Tuve que usar vistas dinámicas (configurar vistas y restricciones por código) y cuando quise establecer el ancho de la etiqueta preferredMaxLayoutWidth fue 0. Así que me equivoqué de altura de celda.

Luego agregué

[cell layoutSubviews];

Antes de ejecutar

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

Después de que el ancho de la etiqueta era como se esperaba y la altura dinámica se calculaba a la derecha.

 9
Author: Mansurov Ruslan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-01 22:37:52

Digamos que tiene una celda con un subview, y desea que la altura de la celda sea lo suficientemente alta como para abarcar el subview + padding.

1) Establezca la restricción inferior del subview igual a la celda.contentView menos el relleno que desee. No establezca restricciones en la celda o celda.contentView en sí.

2) Establezca la propiedad rowHeight de la vista de tabla o tableView:heightForRowAtIndexPath: en UITableViewAutomaticDimension.

3) Establezca la propiedad estimatedRowHeight de la vista de tabla o tableView:estimatedHeightForRowAtIndexPath: a una mejor estimación de la altura.

Eso es se.

 6
Author: Vadoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-18 17:37:44

Si realiza el diseño mediante programación, esto es lo que debe considerar para iOS 10 utilizando 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 dejar que la aplicación calcule el tamaño del indicador de barra de desplazamiento, por lo que ayuda para el rendimiento.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NÚMERO 2: Esto es importante que usted necesita agregar el subviews al contentView de la celda no a la vista, y también use su layoutsmarginguide para anclar las subviews a la parte superior e inferior, este es un ejemplo de trabajo 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 subviews y realice el layout, 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 hace, anulará su implementación.

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

Aquí hay un aplicación de trabajo https://github.com/jamesrochabrun/MinimalViewController

 3
Author: James Rochabrun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-28 15:59:47

En mi caso tengo que crear una celda personalizada con una imagen que viene del servidor y puede ser de cualquier ancho y alto. Y dos UILabels con tamaño dinámico(ambos ancho y alto)

He logrado lo mismo aquí en mi respuesta con autolayout y programáticamente:

Básicamente por encima de @smileyBorg respuesta ayudó pero systemLayoutSizeFittingSize nunca funcionó para mí, En mi enfoque:

1. No se utiliza la propiedad automática de cálculo de altura de fila. 2.No uso de la altura estimada 3.No necesidad de pruebas de actualización innecesarias. 4.No uso del Ancho Máximo de Diseño Preferido Automático. 5. No hay uso de systemLayoutSizeFittingSize (debería tener uso pero no funciona para mí, no sé lo que está haciendo internamente), sino que mi método -(float)getViewHeight funciona y sé lo que está haciendo internamente.

¿Es posible tener diferentes alturas en una celda UITableView cuando uso varias formas diferentes de mostrar la celda?

 0
Author: Alok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:39

En mi caso, el relleno se debió a las alturas de sectionHeader y sectionFooter, donde storyboard me permitió cambiarlo al mínimo 1. Así que en el método viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0
 0
Author: Rashid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-25 07:35:59

Acabo de hacer un intento tonto y error con los 2 valores de rowHeight y estimatedRowHeight y pensé que podría proporcionar alguna información de depuración:

Si establece ambos O solo establece el estimatedRowHeight obtendrá el comportamiento deseado:

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

Se sugiere que haga todo lo posible para obtener la estimación correcta, pero el resultado final no es diferente. Solo afectará tu rendimiento.

introduzca la descripción de la imagen aquí


Si solo establece el rowHeight ie solamente do:

tableView.rowHeight = UITableViewAutomaticDimension

Su resultado final no sería el deseado:

introduzca la descripción de la imagen aquí


Si establece el estimatedRowHeight a 1 o menor, entonces se bloqueará independientemente del rowHeight.

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

Me bloqueé con el siguiente mensaje de error:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type
NSException
 0
Author: Honey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-25 01:56:43

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, mediante el uso de la categoría auxiliar 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

 0
Author: railwayparade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-18 22:31:26

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

La solución es mencionada por la respuesta aceptada y algunas otras respuestas. Solo necesita agregar

cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

Encuentro la respuesta de Suragh la más completa y la menos confusa.

Aunque no explique por qué. Hagámoslo.

Introduzca 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 tamaño restricciones. Solo he añadido centerX, restricciones centerY. Pero aún así la etiqueta será dimensionada correctamente ¿Por qué? Debido a contentSize.

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

Luego descomente el paso 1 y observe.

Luego descomente el paso 2 y observe.

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

Lo que puede concluir de todos ¿esto? ¿Qué factores pueden cambiar el contenSize?

  1. Longitud del texto: Si tiene un texto más largo, el ancho de su intrinsicContentSize aumentará
  2. Saltos de línea: Si se agrega \n entonces 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, entonces su ancho se calculará en base a los 25 caracteres
  3. Número de permisos lines: Debe establecer numberOfLines a 0 de lo contrario, no tendrá varias líneas. Su numberOfLines ajustará la altura de su tamaño intrínseco
  4. Haciendo ajustes: Imagine que basado en su texto, el ancho de su intrinsicContentSize era 200 y el alto era 100, pero quería limitar el ancho al contenedor de la etiqueta ¿qué va a hacer? La solución es establecer el ancho deseado. Usted hace eso fijando preferredMaxLayoutWidth a 130 entonces su nuevo intrinsicContentSize tendrá un ancho de aproximadamente 130. La altura obviamente sería más que 100 porque necesitarías más líneas. Dicho esto, si sus restricciones se establecen correctamente, entonces usted no tendrá que utilizar esto en absoluto! Para más sobre esto ver esta respuesta y sus comentarios. Solo necesita usar preferredMaxLayoutWidth si no tiene restricciones que restrinjan el ancho/alto como en uno podría decir "no envuelva el texto a menos que exceda el preferredMaxLayoutWidth". Pero con 100% de certeza si establecer el principio / final y numberOfLines a 0 entonces usted es bueno! Larga historia corta la mayoría de las respuestas aquí que recomiendan usarlo son ERRÓNEAS! No lo necesitas. Necesitarlo es una señal de que sus restricciones no están configuradas correctamente o que simplemente no tiene restricciones

  5. Tamaño de fuente: También tenga en cuenta que si aumenta su tamaño de fuente, entonces la altura del tamaño de fuente intrínseco aumentará. No mostré eso en mi código. Puedes probar eso en tu propio.

Así que volvamos al ejemplo de TableViewCell:

Todo lo que necesita hacer es establecer el numberOfLines a 0 y establecer el preferredMaxLayoutWidth a su tableView's width.

 0
Author: Honey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-09 14:32:30

Simplemente agregue estas dos funciones en su viewcontroller para resolver su problema. Aquí, list es una matriz de cadenas que contiene la cadena de cada fila.

 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
}
 -1
Author: Parth Barot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-16 06:08:31

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
    }
}
 -4
Author: djdance,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 07:20:21