Múltiples UILabels dentro de un auto dimensionamiento UITableViewCell

En esta aplicación para iOS 8 que estoy creando, tengo una vista de tabla y necesito que se auto-redimensionen. Lo implementé usando Auto Layout y funciona. Casi. Así es como se ve ahora.

enter image description here

Hay 3 tags dentro de una celda. Etiqueta principal que tiene el texto lorem ipsum. Subtítulo que tiene la secuencia de números (Esos son dos rótulos separados. Puede ser confuso porque tienen el mismo color). Luego, la tercera etiqueta con el pequeño texto negro.

La primera etiqueta se redimensionó correctamente sin problemas y la segunda etiqueta se mueve hacia arriba y hacia abajo en consecuencia. Pero el problema es con la tercera etiqueta pequeña. Como puede ver, no se está redimensionando para ajustarse a todo el texto.

Ahora está sucediendo algo extraño. Lo convierto en paisaje y aquí está.

enter image description here

Como hay espacio, la etiqueta muestra todo el texto que se supone que debe. Multa. Luego lo regreso a retrato.

enter image description here

Ahora la etiqueta pequeña se ha redimensionado para ajustarse a todo su texto, pero desborda los límites de las celdas. Traté de agrandar la celda, pero no funcionó. Como se trata de células auto dimensionantes, no creo que esa sea la forma correcta ni siquiera.

No estoy recibiendo ningún error o incluso advertencia sobre mis limitaciones de diseño automático tampoco.

enter image description here

He establecido estas dos líneas de código en el método viewDidLoad() .

 tableView.estimatedRowHeight = 100 tableView.rowHeight = UITableViewAutomaticDimension 

¿Puede alguien decirme por favor qué podría estar haciendo mal aquí?

Dado que es difícil de responder con solo mirar imágenes y no tengo más código para publicar junto al fragmento de arriba, cargué un proyecto Xcode ejecutable que demuestra el problema aquí . (Hay 2 celdas personalizadas. Básicamente es la misma celda, solo la altura se incrementa en la segunda).

He estado jugando con restricciones de diseño automático, pero parece que no puedo hacer que esto funcione. Cualquier ayuda sería apreciada.

Gracias.


ACTUALIZAR:

Con la ayuda de este tutorial encontré algunos consejos útiles. De acuerdo con esto, cada subvista debe tener restricciones que fijen todos sus lados y debe haber restricciones que vayan de arriba a abajo, lo que ayuda al diseño automático a calcular la altura de la celda. En mi publicación original, tenía espacios verticales entre cada etiqueta, así que creo que esa es la razón por la que el diseño automático no pudo calcular la altura adecuada.

Entonces hice algunos cambios.

  • Reduje el espacio vertical entre tags a 0 y establezco las restricciones de espacio vertical entre las tags superiores e intermedias y las tags medias e inferiores.
  • Agregué las principales restricciones finales a la etiqueta superior.
  • Liderando y siguiendo a la etiqueta del medio.
  • Liderando, abajo, siguiendo la etiqueta inferior.

Ahora aquí hay otra parte extraña. Cuando lo ejecuto por primera vez, el tema del cultivo de tags está todavía ahí.

enter image description here

Pero si giro el dispositivo al paisaje y lo vuelvo a hacer de retrato, ¡todas las celdas se redimensionan correctamente para ajustarse a ambas tags!

enter image description here

Todavía no puedo entender por qué esto no sucede al principio. El proyecto actualizado de Xcode está aquí .

El problema aquí es con la propiedad preferredMaxLayoutWidth las tags de varias líneas. Esta es la propiedad que le dice a la etiqueta cuándo debería envolverse la palabra. Debe configurarse correctamente para que la etiqueta intrinsicContentSize tenga la altura correcta, que es en definitiva lo que Auto Layout usará para determinar la altura de la celda.

Xcode 6 Interface Builder introdujo una nueva opción para tener esta propiedad establecida en Automático . Desafortunadamente, hay algunos errores graves (a partir de Xcode 6.2 / iOS 8.2) en los que esto no se configura correctamente / automáticamente al cargar una celda desde una punta o Storyboard.

Para evitar este error, necesitamos que el conjunto preferredMaxLayoutWidth sea ​​exactamente igual al ancho final de la etiqueta una vez que se muestre en la vista de tabla. Efectivamente, queremos hacer lo siguiente antes de devolver la celda de tableView:cellForRowAtIndexPath: ::

 cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame) cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame) cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame) 

La razón por la que simplemente agregar este código por sí solo no funciona es porque cuando estas 3 líneas de código se ejecutan en tableView:cellForRowAtIndexPath: estamos utilizando el ancho de cada etiqueta para establecer el tableView:cellForRowAtIndexPath: preferredMaxLayoutWidth ; sin embargo, si comprueba el ancho del tags en este punto en el tiempo, el ancho de la etiqueta es totalmente diferente de lo que terminará siendo una vez que se muestra la celda y se han presentado sus subvistas.

¿Cómo hacemos que los anchos de las tags sean precisos en este punto, para que reflejen su ancho final? Aquí está el código que hace que todo se una:

 // Inside of tableView:cellForRowAtIndexPath:, after dequeueing the cell cell.bounds = CGRect(x: 0, y: 0, width: CGRectGetWidth(tableView.bounds), height: 99999) cell.contentView.bounds = cell.bounds cell.layoutIfNeeded() cell.nameLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.nameLabel.frame) cell.idLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.idLabel.frame) cell.actionsLabel.preferredMaxLayoutWidth = CGRectGetWidth(cell.actionsLabel.frame) 

OK, entonces, ¿qué estamos haciendo aquí? Bueno, notará que hay 3 nuevas líneas de código agregadas. Primero, necesitamos establecer el ancho de esta celda de vista de tabla para que coincida con el ancho real de la vista de tabla (esto supone que la vista de tabla ya se ha trazado y tiene su ancho final, que debería ser el caso). De hecho, estamos haciendo que el ancho de la celda sea correcto desde el principio, ya que la vista de tabla lo hará eventualmente.

También notará que estamos usando 99999 para la altura. ¿Sobre qué trata? Esa es una solución simple para el problema discutido en detalle aquí , donde si sus restricciones requieren más espacio vertical que la altura actual del contenido de la celda, se obtiene una excepción de restricción que en realidad no indica ningún problema real. La altura de la celda o cualquiera de sus subvistas en realidad no importa en este punto, porque solo nos importa obtener el ancho final para cada etiqueta.

A continuación, nos aseguramos de que contentView de la celda tenga el mismo tamaño que el que acabamos de asignar a la propia celda, configurando los límites de contentView para que sean iguales a los límites de la celda. Esto es necesario porque todas las restricciones de diseño automático que ha creado son relativas a contentView, por lo que contentView debe tener el tamaño correcto para que se resuelvan correctamente. Simplemente establecer manualmente el tamaño de la celda no ajusta automáticamente el tamaño de la vista de contenido para que coincida.

Finalmente, forzamos un pase de disposición en la celda, que hará que el motor de diseño automático resuelva sus limitaciones y actualice los marcos de todas las subvistas. Dado que cell y contentView ahora tienen el mismo ancho que en tiempo de ejecución en la vista de tabla, los anchos de etiqueta también serán correctos, lo que significa que el preferredMaxLayoutWidth establecido para cada etiqueta será preciso y hará que la etiqueta se ajuste en el momento correcto. , lo que por supuesto significa que las alturas de las tags se establecerán correctamente cuando la celda se use en la vista de tabla.

Este es definitivamente un error de Apple en UIKit que tenemos que solucionar por ahora (así que por favor hagan informes de errores de archivos con Apple para que den prioridad a una solución).

Una nota final: esta solución alternativa tendrá problemas si el ancho de contentView la celda de la vista de contentView no se extiende a todo el ancho de la vista de tabla, por ejemplo, cuando hay un índice de sección que se muestra a la derecha. En este caso, deberá asegurarse de tener esto en cuenta manualmente al configurar el ancho de la celda; es posible que necesite codificar estos valores, como:

 let cellWidth = CGRectGetWidth(tableView.bounds) - kTableViewSectionIndexWidth cell.bounds = CGRect(x: 0, y: 0, width: cellWidth, height: 99999) 

Encontré el mismo problema que tú y encontré una solución simple para resolverlo.

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // dequeue cell... // do autolayout staffs...or if the autolayout rule has been set in xib, do nothing [cell layoutIfNeeded]; return cell; } 

Y el auto-tamaño funcionó bien. En mi código, puse dos tags en vertical, ambas son de altura dinámica. La altura de la celda está configurada correctamente para contener las dos tags.

Suponiendo que no tiene ningún error con sus limitaciones como otros han sugerido, este problema parece provenir del uso de un UILabel que permite múltiples líneas junto con un UITableViewCellAccessory. Cuando iOS establece la celda y determina la altura, no tiene en cuenta el cambio de desplazamiento en el ancho que ocurre debido a este accesorio, y obtiene el truncamiento donde no lo esperaría.

Asumiendo que quiera que UILabel extienda todo el ancho de la vista de contenido, escribí un método que corrige esto para todos los tamaños de fuente

 -(void)fixWidth:(UILabel *)label forCell:(UITableViewCell *)cell { float offset = 0; switch ([cell accessoryType]) { case UITableViewCellAccessoryCheckmark: offset = 39.0; break; case UITableViewCellAccessoryDetailButton: offset = 47.0; break; case UITableViewCellAccessoryDetailDisclosureButton: offset = 67.0; break; case UITableViewCellAccessoryDisclosureIndicator: offset = 33.0; break; case UITableViewCellAccessoryNone: offset = 0; break; } [label setPreferredMaxLayoutWidth:CGRectGetWidth([[self tableView]frame]) - offset - 8]; } 

Simplemente ponga esto en su cellForRowAtIndexPath

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { #Setup the cell ... // Fix layout with accessory view [self fixWidth:[cell label] forCell:cell]; return cell; } 

Haga esto para cualquier etiqueta que vaya a tener varias líneas para ajustar el ancho correctamente y luego vuelva a calcular las alturas apropiadas. Esto también funciona con tamaños dynamics de fonts.

Como smileyborg había mencionado, si no usabas el ancho completo de contentView, podrías hacer referencia a las restricciones y restarlas del ancho también.

Editar: Anteriormente estaba ejecutando ‘layoutIfNeeded’ en la celda, pero esto estaba creando problemas de rendimiento y no parecía ser necesario de todos modos. Eliminarlo no me ha causado ningún problema.

Tienes dos problemas aquí.

1 / En este momento, usando el lenguaje de formato visual, las restricciones verticales de su celda se pueden traducir así:

 Cell: "V:|-(10)-[nameLabel]-(67)-|" 

Luego, establece un segundo grupo de restricciones:

 Cell: "V:|-(10)-[nameLabel]-(8)-[pnrLabel]-(2)-[actionsLabel]" 

Esos dos grupos de restricciones no se pueden mezclar bien y revelarán su ambigüedad con su segundo problema.

2 / Por alguna razón, actionsLabel está limitado a una línea cuando actionsLabel su aplicación. Luego, cuando gira su dispositivo al modo horizontal, actionsLabel acepta que se muestre con dos líneas o más. Luego, cuando gira su dispositivo nuevamente al modo vertical, actionsLabel sigue mostrando dos líneas o más. Pero, como actionsLabel no es realmente parte de las restricciones de altura de su celda, se superpone a los límites de su celda.

Si quiere resolver todos esos problemas, le recomiendo primero que reconstruya su archivo xib desde cero. Esto curará sus actionsLabel . actionsLabel comportamiento extraño (dos líneas o más solo cuando gira el dispositivo).

Entonces, tendrás que definir tus restricciones de esta manera:

 Cell: "V:|-(10)-[nameLabel(>=21)]-(8)-[pnrLabel(>=21)]-(2)-[actionsLabel(>=21)]-(10)-|" 

Por supuesto, puede definir otras restricciones de altura mínima para las tags que (>=21) . De la misma manera, su margen inferior se puede establecer en otro valor que -(10)- .

Apéndice

Para responder a su pregunta, creé un proyecto simple con el patrón de restricciones anterior en mi archivo .xib. La siguiente imagen puede ayudarte a construir tus propias restricciones.

enter image description here

Probé la solución de “fogisland”, que es muy sencilla y elegante, y no funcionó. Afortunadamente, descubrí que una línea adicional lo hace funcionar en todas las direcciones. Simplemente dígale al sistema que no solo sugiere un nuevo diseño (layout IfNeeded), explícitamente lo solicita (setNeedLayout)

  cell.setNeedsLayout() cell.layoutIfNeeded() return cell