TL; DR: ¿No te gusta leer? Vaya 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 que esté desarrollando.
1. Configurar y agregar restricciones
En su UITableViewCell
subclase, agregue restricciones para que las subvistas de la celda tengan sus bordes fijados en los bordes de contentView de la celda (lo más importante en los bordes superior e inferior). NOTA: no ancle subvistas a la celda misma; solo a la celda 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 la resistencia de compresión de contenido y las restricciones de abrazo de contenido en la dimensión vertical para cada subvista no se anulen por las restricciones de mayor prioridad que ha agregado. ( ¿Eh? Haz clic aquí. )
Recuerde, la idea es tener las subvistas de la celda conectadas verticalmente a la vista de contenido de la celda para que puedan "ejercer presión" y hacer que la vista de contenido se expanda para adaptarse a ellas. Usando una celda de ejemplo con algunas subvistas, aquí hay una ilustración visual de cómo deberían verse algunas (¡no todas!) De sus restricciones:
Puede imaginar 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 forzará efectivamente que la celda crezca en altura. (¡Por supuesto, debe obtener las restricciones correctas para que esto funcione correctamente!)
Definir correctamente sus restricciones es definitivamente la parte más difícil y más importante para obtener alturas de celda dinámicas trabajando con Auto Layout. 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 y 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 poderoso que Interface Builder que utiliza anclas de diseño o una de las fantásticas API de código abierto disponibles en GitHub.
- Si agrega restricciones en el código, debe hacerlo una vez desde el
updateConstraints
método de su subclase UITableViewCell. Tenga en cuenta que updateConstraints
se puede llamar más de una vez, por lo que para evitar agregar las mismas restricciones más de una vez, asegúrese de ajustar el código de adición de restricciones dentro updateConstraints
de un cheque para una propiedad booleana como didSetupConstraints
(que establece en SÍ después de ejecutar su restricción -adición de código una vez). Por otro lado, si tiene un código que actualiza las restricciones existentes (como ajustar la constant
propiedad en algunas restricciones), colóquelo dentro updateConstraints
pero fuera de la verificación para didSetupConstraints
que pueda ejecutarse cada vez que se llame al método.
2. Determine los identificadores únicos de reutilización de celdas de vista de tabla
Para cada conjunto único de restricciones en la celda, use un identificador de reutilización de celda único. En otras palabras, si sus celdas tienen más de un diseño único, cada diseño único debe recibir su propio identificador de reutilización. (Un buen indicio de que necesita usar un nuevo identificador de reutilización es cuando la variante de celda tiene un número diferente de subvistas, o las subvistas se organizan de manera distinta).
Por ejemplo, si estaba mostrando un mensaje de correo electrónico en cada celda, podría tener 4 diseños únicos: mensajes con solo un asunto, mensajes con un asunto y un cuerpo, mensajes con un asunto y un archivo adjunto de foto, y mensajes con un asunto, cuerpo y adjunto de foto. Cada diseño tiene restricciones completamente diferentes necesarias 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 de reutilización único específico para ese tipo de celda. Esto significa que cuando retira una celda para su reutilización, las restricciones ya se han agregado y están listas para ese tipo de celda.
Tenga en cuenta que debido a las diferencias en el tamaño del contenido intrínseco, ¡las celdas con las mismas restricciones (tipo) aún pueden tener alturas variables! No confunda diseños fundamentalmente diferentes (diferentes restricciones) con diferentes marcos de vista calculados (resueltos a partir de restricciones idénticas) debido a 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 establecer nuevas restricciones desde cero después de cada cola. El motor de diseño automático interno no está diseñado para manejar cambios a gran escala en las restricciones, y verá problemas de rendimiento masivos.
Para iOS 8: celdas de tamaño automático
3. Habilite la estimación de altura de fila
Para habilitar las celdas de vista de tabla de tamaño automático, debe establecer la propiedad rowHeight de la vista de tabla en UITableViewAutomaticDimension. También debe asignar un valor a la propiedad estimadaRowHeight. Tan pronto como se establecen estas dos propiedades, el sistema usa el diseño automático para calcular la altura real de la fila
Apple: trabajar con celdas de vista de tabla de tamaño automático
Con iOS 8, Apple ha internalizado gran parte del trabajo que antes tenía que implementar antes de iOS 8. Para permitir que funcione el mecanismo de celda de tamaño automático, primero debe establecer la rowHeight
propiedad en la vista de tabla a la constante UITableViewAutomaticDimension
. Luego, simplemente necesita habilitar la estimación de altura de fila configurando la estimatedRowHeight
propiedad de la vista de tabla en un valor distinto de cero, por ejemplo:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
Lo que esto hace es proporcionar a la vista de tabla una estimación temporal / marcador de posición para las alturas de fila de las celdas que aún no están en pantalla. Luego, cuando estas celdas estén a punto de desplazarse en la pantalla, se calculará la altura real de la fila. Para determinar la altura real de cada fila, la vista de tabla pregunta automáticamente a cada celda qué altura contentView
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 elemento adicional como un índice de sección o vista de accesorios) y las restricciones de diseño automático que ha agregado a la vista de contenido y subvistas de la celda. Una vez que se ha determinado esta altura real de la celda, la altura estimada anterior para la fila se actualiza con la nueva altura real (y cualquier ajuste al contenido de la vista de tabla contentSize / contentOffset se realiza según sea necesario).
En términos generales, la estimación que proporciona no tiene que ser muy precisa: solo se usa para dimensionar correctamente el indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo al ajustar el indicador de desplazamiento para estimaciones incorrectas como usted Desplazar celdas en pantalla. Debe establecer la estimatedRowHeight
propiedad en la vista de tabla (en viewDidLoad
o similar) a un valor constante que sea el alto de fila "promedio". Solo si las alturas de sus filas tienen una variabilidad extrema (por ejemplo, difieren en un orden de magnitud) y nota que el indicador de desplazamiento "salta" a medida que se desplaza, debe molestarse en implementar tableView:estimatedHeightForRowAtIndexPath:
para hacer el cálculo mínimo requerido para devolver una estimación más precisa para cada fila.
Para soporte de iOS 7 (implementando el dimensionamiento automático de la celda usted mismo)
3. Haga un pase de diseño y obtenga 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 los cálculos de altura. (Fuera de la tableView:cellForRowAtIndexPath:
pantalla, lo que significa que la referencia de celda se almacena en una propiedad / ivar en el controlador de vista y nunca se devuelve para que la vista de tabla se muestre en la pantalla). A continuación, la celda se debe configurar con el contenido exacto (por ejemplo, texto, imágenes, etc.) que se mantendría si se mostrara en la vista de tabla.
Luego, fuerce a la celda a diseñar inmediatamente sus subvistas, y luego use el systemLayoutSizeFittingSize:
método en la UITableViewCell
's contentView
para averiguar cuál es la altura requerida de la celda. Úselo 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. Use las alturas de fila estimadas
Si la vista de la tabla tiene más de un par de docenas de filas, descubrirá que al hacer la resolución de restricciones de Diseño automático puede atascarse rápidamente el subproceso principal cuando se carga por primera vez la vista de la tabla, como tableView:heightForRowAtIndexPath:
se llama en cada fila en la primera carga ( para calcular el tamaño del indicador de desplazamiento).
A partir de iOS 7, puede (y absolutamente debe) usar la estimatedRowHeight
propiedad en la vista de tabla. Lo que esto hace es proporcionar a la vista de tabla una estimación temporal / marcador de posición para las alturas de fila de las celdas que aún no están en pantalla. Luego, cuando estas celdas están a punto de desplazarse en la pantalla, se calculará la altura real de la fila (llamando tableView:heightForRowAtIndexPath:
) y la altura estimada se actualizará con la real.
En términos generales, la estimación que proporciona no tiene que ser muy precisa: solo se usa para dimensionar correctamente el indicador de desplazamiento en la vista de tabla, y la vista de tabla hace un buen trabajo al ajustar el indicador de desplazamiento para estimaciones incorrectas como usted Desplazar celdas en pantalla. Debe establecer la estimatedRowHeight
propiedad en la vista de tabla (en viewDidLoad
o similar) a un valor constante que sea el alto de fila "promedio". Solo si las alturas de sus filas tienen una variabilidad extrema (por ejemplo, difieren en un orden de magnitud) y nota que el indicador de desplazamiento "salta" a medida que se desplaza, debe molestarse 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) Agregue el almacenamiento en caché de altura de fila
Si ha hecho todo lo anterior y todavía encuentra que el rendimiento es inaceptablemente lento cuando se resuelve la restricción tableView:heightForRowAtIndexPath:
, desafortunadamente deberá implementar un almacenamiento en caché para las alturas de las celdas. (Este es el enfoque sugerido por los ingenieros de Apple.) La idea general es dejar que el motor de Autolayout resuelva las restricciones la primera vez, luego almacene en caché la altura calculada para esa celda y use el valor en caché para todas las solicitudes futuras para la altura de esa celda. El truco, por supuesto, es asegurarse de borrar la altura en caché de una celda cuando ocurra algo que pueda hacer que cambie la altura de la celda; principalmente, esto será cuando cambie el contenido de esa celda o cuando ocurran otros eventos importantes (como el ajuste del usuario el control deslizante de tamaño de texto de tipo dinámico).
Código de muestra genérico de iOS 7 (con muchos comentarios jugosos)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline 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 totalmente funcionales de vistas de tabla con alturas de fila variables debido a las celdas de vista de tabla que contienen contenido dinámico en UILabels.
Xamarin (C # / .NET)
Si está utilizando Xamarin, consulte este proyecto de muestra elaborado por @KentBoogaart .