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


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


1350
2017-09-11 16:48


origen


Respuestas:


TL; DR: No me gusta leer? Salta directamente a los proyectos de muestra 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 agregar restricciones

En tus UITableViewCell subclase, agregue restricciones para que las subvistas de la celda tengan sus bordes fijados a los bordes de la celda contentView (lo más importante para los bordes superiores e inferiores). NOTA: no anote las subvistas a la celda misma; solo a la célula contentView! Deje que el tamaño de contenido intrínseco de estas subvistas controle la altura de la vista de contenido de la celda de la vista de tabla asegurándose de que resistencia a la compresión del contenido y contenido abrazando las restricciones en la dimensión vertical para cada subvista no se anulan por las restricciones de mayor prioridad que ha agregado. (¿Huh? 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 hacer que la vista de contenido se expanda para ajustarse a ellas. Usando una celda de ejemplo con algunas subvistas, aquí hay una ilustración visual de algunos  (¡no todo!) de sus limitaciones tendría que ser similar a:

Example illustration of constraints on a table view cell.

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

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

  • Si está agregando restricciones en el código, debe hacer esto una vez desde dentro del updateConstraints método de su subclase UITableViewCell. Tenga en cuenta que updateConstraints puede llamarse más de una vez, por lo que para evitar agregar las mismas restricciones más de una vez, asegúrese de ajustar su código de restricción updateConstraints en un cheque para una propiedad booleana como didSetupConstraints (que configura en SÍ después de ejecutar el código de adición de restricciones una vez). Por otro lado, si tiene un código que actualiza las restricciones existentes (como el ajuste del constantpropiedad en algunas restricciones), coloque esto en updateConstraints pero fuera del cheque de didSetupConstraints para que pueda 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 de reutilización de celda único. En otras palabras, si sus celdas tienen más de un diseño único, cada diseño único debería recibir su propio identificador de reutilización. (Una buena sugerencia de que necesita usar un nuevo identificador de reutilización es cuando la variante de su celda tiene un número diferente de subvistas, o las subvistas se organizan de una manera distinta).

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

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

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

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

3. Habilitar la estimación de altura de fila

Para habilitar celdas de vista de tabla de tamaño propio, debe establecer la vista de tabla   propiedad rowHeight a UITableViewAutomaticDimension. Usted también debe   asignar un valor a la propiedad de estimaciónRowHeight. Tan pronto como ambos de   estas propiedades están configuradas, el sistema usa Diseño automático para calcular el   altura real de la fila

Manzana: Trabajar con celdas de vista de tabla de tamaño automático

Con iOS 8, Apple ha internalizado gran parte del trabajo que anteriormente tenía que implementar antes de iOS 8. Para permitir que funcione el mecanismo de auto-tamaño de la celda, primero debe configurar el rowHeight propiedad en la vista de tabla a la constante UITableViewAutomaticDimension. Entonces, simplemente necesita habilitar la estimación de altura de fila configurando la vista de tabla estimatedRowHeight propiedad 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 hace es proporcionar a la vista de tabla una estimación temporal / marcador de posición para las alturas de filas de las celdas que todavía no están en pantalla. Luego, cuando estas celdas estén a punto de desplazarse por la pantalla, se calculará la altura real de la fila. Para determinar la altura real de cada fila, la vista de tabla pregunta automáticamente a cada celda qué altura es contentViewdebe basarse en el ancho fijo conocido de la vista de contenido (que se basa en el ancho de la vista de tabla, menos cualquier elemento adicional como un índice de sección o vista de accesorio) y las restricciones de diseño automático que ha agregado a la vista y subvistas de contenido de la celda . Una vez que se ha determinado la altura real de la celda, la altura estimada anterior para la fila se actualiza con la nueva altura real (y cualquier ajuste al contentSize / contentOffset de la vista de tabla se realiza según sea necesario para usted).

En general, la estimación que proporciona no tiene que ser muy precisa: solo se utiliza para ajustar correctamente el tamaño del indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo ajustando el indicador de desplazamiento para estimaciones incorrectas a medida que desplazarse por las celdas en pantalla. Debes configurar el estimatedRowHeight propiedad 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 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: hacer el cálculo mínimo requerido para devolver una estimación más precisa para cada fila.

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

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

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

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

4. Utilice alturas de fila estimadas

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

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

En general, la estimación que proporciona no tiene que ser muy precisa: solo se utiliza para ajustar correctamente el tamaño del indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo ajustando el indicador de desplazamiento para estimaciones incorrectas a medida que desplazarse por las celdas en pantalla. Debes configurar el estimatedRowHeight propiedad 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 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: 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 ha hecho todo lo anterior y aún encuentra que el rendimiento es inaceptablemente lento al hacer la resolución de restricciones en tableView:heightForRowAtIndexPath:, desafortunadamente necesitará implementar 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 por primera vez, luego almacenar en caché la altura calculada para esa celda y usar el valor almacenado en caché para todas las solicitudes futuras para la altura de esa celda. El truco, por supuesto, es asegurarse de borrar la altura almacenada en caché de una celda cuando ocurre cualquier cosa que pueda cambiar la altura de la celda; principalmente, cuando el contenido de esa celda cambia o cuando ocurren otros eventos importantes (como el ajuste del usuario). el control deslizante de tamaño de texto de tipo dinámico).

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Determine which reuse identifier should be used for the cell at this 
    // index path, depending on the particular layout required (you may have
    // just one, or may have many).
    NSString *reuseIdentifier = ...;

    // Dequeue a cell for the reuse identifier.
    // Note that this method will init and return a new cell if there isn't
    // one available in the reuse pool, so either way after this line of 
    // code you will have a cell with the correct constraints ready to go.
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];

    // Configure the cell with content for the given indexPath, for example:
    // cell.textLabel.text = someTextForThisCell;
    // ...

    // Make sure the constraints have been set up for this cell, since it 
    // may have just been created from scratch. Use the following lines, 
    // assuming you are setting up constraints from within the cell's 
    // updateConstraints method:
    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    // If you are using multi-line UILabels, don't forget that the 
    // preferredMaxLayoutWidth needs to be set correctly. Do it at this 
    // point if you are NOT doing it within the UITableViewCell subclass 
    // -[layoutSubviews] method. For example: 
    // cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Determine which reuse identifier should be used for the cell at this 
    // index path.
    NSString *reuseIdentifier = ...;

    // Use a dictionary of offscreen cells to get a cell for the reuse 
    // identifier, creating a cell and storing it in the dictionary if one 
    // hasn't already been added for the reuse identifier. WARNING: Don't 
    // call the table view's dequeueReusableCellWithIdentifier: method here 
    // because this will result in a memory leak as the cell is created but 
    // never returned from the tableView:cellForRowAtIndexPath: method!
    UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
    if (!cell) {
        cell = [[YourTableViewCellClass alloc] init];
        [self.offscreenCells setObject:cell forKey:reuseIdentifier];
    }

    // Configure the cell with content for the given indexPath, for example:
    // cell.textLabel.text = someTextForThisCell;
    // ...

    // Make sure the constraints have been set up for this cell, since it 
    // may have just been created from scratch. Use the following lines, 
    // assuming you are setting up constraints from within the cell's 
    // updateConstraints method:
    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    // Set the width of the cell to match the width of the table view. This
    // is important so that we'll get the correct cell height for different
    // table view widths if the cell's height depends on its width (due to 
    // multi-line UILabels word wrapping, etc). We don't need to do this 
    // above in -[tableView:cellForRowAtIndexPath] because it happens 
    // automatically when the cell is used in the table view. Also note, 
    // the final width of the cell may not be the width of the table view in
    // some cases, for example when a section index is displayed along 
    // the right side of the table view. You must account for the reduced 
    // cell width.
    cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));

    // Do the layout pass on the cell, which will calculate the frames for 
    // all the views based on the constraints. (Note that you must set the 
    // preferredMaxLayoutWidth on multi-line UILabels inside the 
    // -[layoutSubviews] method of the UITableViewCell subclass, or do it 
    // manually at this point before the below 2 lines!)
    [cell setNeedsLayout];
    [cell layoutIfNeeded];

    // Get the actual height required for the cell's contentView
    CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    // Add an extra point to the height to account for the cell separator, 
    // which is added between the bottom of the cell's contentView and the 
    // bottom of the table view cell.
    height += 1.0;

    return height;
}

// NOTE: Set the table view's estimatedRowHeight property instead of 
// implementing the below method, UNLESS you have extreme variability in 
// your row heights and you notice the scroll indicator "jumping" 
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Do the minimal calculations required to be able to return an 
    // estimated row height that's within an order of magnitude of the 
    // actual height. For example:
    if ([self isTallCellAtIndexPath:indexPath]) {
        return 350.0;
    } else {
        return 40.0;
    }
}

Proyectos de muestra

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

Xamarin (C # /. NET)

Si estás usando Xamarin, mira esto proyecto de muestra juntos por @KentBoogaart.


2265
2018-06-19 08:21



Para IOS8 es realmente simple:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

O

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

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

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

Importante 

  • Si hay varias líneas de etiquetas, no olvide configurar el numberOfLines a 0.

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

El código de ejemplo completo es aquí.


122
2018-04-05 09:23



Ejemplo rápido de altura variable UITableViewCell

Actualizado para Swift 3

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

El proyecto final debe verse así:

enter image description here

Crea un nuevo proyecto

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

Agrega el código

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

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

Conectaremos esta salida más tarde.

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

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

Nota IMPORTANTE:

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

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Configura el guión gráfico

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

enter image description here

Nota IMPORTANTE:

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

Otros ajustes de IB

Nombre de clase personalizado e identificador

Seleccione la celda de vista de tabla y configure la clase personalizada que se va a MyCustomCell (el nombre de la clase en el archivo Swift que agregamos). Configure también el Identificador para ser cell(la misma cadena que usamos para el cellReuseIdentifier en el código de arriba.

enter image description here

Cero líneas para etiqueta

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

enter image description here 

Enganche los enchufes

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

Terminado

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

Notas

  • Este ejemplo solo funciona para iOS 8 y posteriores. Si todavía necesita soportar iOS 7, esto no funcionará para usted.
  • Sus propias celdas personalizadas en sus proyectos futuros probablemente tendrán más de una etiqueta. Asegúrate de tener todo anclado correctamente para que el diseño automático pueda determinar la altura correcta para usar. También puede que tenga que usar resistencia de compresión vertical y abrazos. Ver 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 deba configurar los rótulos preferredMaxLayoutWidth para que sepa cuándo ajustar la línea. Por ejemplo, si ha agregado una restricción Centro horizontalmente a la etiqueta en el proyecto anterior en lugar de fijar los bordes anterior y posterior, entonces deberá agregar esta línea al tableView:cellForRowAtIndexPath método:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Ver también


71
2018-06-11 15:18



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

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

La categoría también rectifica los problemas descritos en la respuesta de @ wildmonkey (cargando una celda de un plumón y systemLayoutSizeFittingSize: regresando CGRectZero)

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

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

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


60
2018-02-10 19:41



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

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

43
2017-11-11 16:52



La solución propuesta por @smileyborg es casi perfecta. Si tiene una celda personalizada y quiere una o más UILabel con alturas dinámicas, entonces el systemLayoutSizeFittingSize método combinado con AutoLayout habilitado devuelve un CGSizeZero a menos que mueva todas sus restricciones de celda de la celda a su contenido (como lo sugiere @TomSwift aquí) ¿Cómo cambiar el tamaño de la supervista para que se ajuste a 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.


42
2017-12-03 02:23



Un gotcha lo suficientemente importante que encontré para publicar como respuesta.

La respuesta de @Sandyborg es en su mayoría correcta. Sin embargo, si tiene algún código en el layoutSubviews método de su clase de celda personalizada, por ejemplo, configurar 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 eso solo está 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 setNeedsLayout como ya debería estar establecido. En los casos donde guarda una referencia a una celda, probablemente debería llamarla. De cualquier forma, no debería doler nada.

Otro consejo si está usando subclases de celdas donde está configurando cosas como preferredMaxLayoutWidth. Como @smileyborg menciona, "su celda de vista de tabla aún no tiene su ancho fijo en el ancho de la vista de tabla". Esto es cierto y tiene problemas si realiza 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);

(Resulta que guardo en caché esta celda en particular para su reutilización, pero eso es irrelevante).


22
2018-03-05 15:53



En caso de que las personas sigan teniendo problemas con esto. Escribí una breve publicación en el blog sobre el uso de Autolayout con UITableViews Aprovechamiento de Autolayout para Dynamic Cell Heights así como un componente de fuente abierta para ayudar a que esto sea más abstracto y más fácil de implementar. https://github.com/Raizlabs/RZCellSizeManager


19
2018-06-27 22:49



(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 automática de la celda de tamaño. Incluso si sus restricciones son absolutamente válidas, IB aún se quejará 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 cambio, mantiene fija la altura de la fila y comienza a sugerirle que cambie sus restricciones, lo cual debes ignorar.

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

enter image description here

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

enter image description here

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

Si Interface Builder funcionaba 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 sucede en realidad es que IB muestra el ícono rojo de error de diseño automático y sugiere que modifique las prioridades de abrazos / compresión.

enter image description here

Debes 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> Altura de la fila).

enter image description here

Estaba cambiando esta altura un clic a la vez (usando el paso a paso arriba / abajo) hasta que los errores de la flecha roja desaparecen. (en realidad recibirás advertencias amarillas, en cuyo punto simplemente haz clic en 'actualizar marcos', todo debería funcionar).

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

De hecho, tratar de actualizar siempre la altura de la fila en IB es muy 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 escoger Verify Position Only

enter image description here


Xcode 8.x / Xcode 9.x parece (a veces) hacer las cosas de manera diferente a Xcode 7.x, pero aún incorrectamente. Por ejemplo, incluso cuando compression resistance priority / hugging priority están configurados en requerido (1000), el 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 encaje alrededor de la etiqueta). Y en tal caso, es posible que ni siquiera muestre advertencias o errores de AutoLayout. O a veces hace exactamente lo que hizo Xcode 7.x, descrito anteriormente.


18
2018-04-29 18:16



Siempre que 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.


16
2017-07-08 01:34



Me gusta @ Bob-Spryn Me encontré con un problema importante que estoy publicando esto como respuesta.

Luché con @ smileyborg's responde por un tiempo El problema con el que me encontré es si definiste tu prototipo de celda en IB con elementos adicionales (UILabels, UIButtons, etc.) en IB cuando crea una instancia de la celda con [[YourTableViewCellClass alloc] init] no instanciará todos los otros elementos dentro de esa celda a menos que haya escrito un código para hacerlo. (Tuve una experiencia similar con initWithStyle.)

Para que el guión gráfico crea una instancia de todos los elementos adicionales, obtenga su celda con [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (No [tableView dequeueReusableCellWithIdentifier:forIndexPath:] ya que esto causará problemas interesantes.) Cuando haga esto, se crearán instancias de todos los elementos que definió en IB.


14
2017-08-22 10:14