UILabel sizeToFit no funciona con autolayout ios6

152

¿Cómo se supone que debo configurar mediante programación (y en qué método) un UILabel cuya altura depende de su texto? He estado tratando de configurarlo usando una combinación de Storyboard y código, pero fue en vano. Todo el mundo recomienda sizeToFital configurar lineBreakModey numberOfLines. Sin embargo, no importa si pongo ese código en viewDidLoad:, viewDidAppear:o viewDidLayoutSubviewsno puedo conseguir que funcione. O hago que el cuadro sea demasiado pequeño para texto largo y no crece, o lo hago demasiado grande y no se encoge.

circuito de lego
fuente
FWIW el mismo código: no necesitaba usar label.sizeToFit()en Xcode / viewController, las restricciones eran suficientes. no estaba creando la etiqueta en Playground . Hasta ahora, la única manera que he encontrado para trabajar en juegos es hacerlabel.sizeToFit()
Miel

Respuestas:

407

Tenga en cuenta que, en la mayoría de los casos, la solución de Matt funciona como se esperaba. Pero si no te funciona, por favor, sigue leyendo.

Para hacer que su etiqueta cambie automáticamente el tamaño de la altura, debe hacer lo siguiente:

  1. Establecer restricciones de diseño para la etiqueta
  2. Establecer restricción de altura con baja prioridad. Debe ser inferior a ContentCompressionResistancePriority
  3. Establecer numberOfLines = 0
  4. Establezca ContentHuggingPriority más alto que la prioridad de altura de la etiqueta
  5. Establezca preferredMaxLayoutWidth para la etiqueta. La etiqueta usa ese valor para calcular su altura

Por ejemplo:

self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;

[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];

NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[descriptionLabel_(220@300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];

Usando Interface Builder

  1. Establecer cuatro restricciones. La restricción de altura es obligatoria. ingrese la descripción de la imagen aquí

  2. Luego vaya al inspector de atributos de la etiqueta y establezca el número de líneas en 0. ingrese la descripción de la imagen aquí

  3. Vaya al inspector de tamaño de la etiqueta y aumente la ContentHuggingPriority vertical y la ContentCompressionResistancePriority vertical.
    ingrese la descripción de la imagen aquí

  4. Seleccione y edite la restricción de altura.
    ingrese la descripción de la imagen aquí

  5. Y disminuir la prioridad de restricción de altura.
    ingrese la descripción de la imagen aquí

Disfrutar. :)

Mark Kryzhanouski
fuente
44
¡SI! Esta es exactamente la respuesta que estaba buscando. Lo único que tenía que hacer era establecer contentHuggingPriority y contentCompressionResistancePriority como requerido. ¡Podría hacerlo todo en IB! Muchas gracias.
circuitlego
43
Estás pensando demasiado en esto. Establezca preferredMaxLayoutWidtho ancle el ancho de la etiqueta (o sus lados derecho e izquierdo). ¡Eso es todo! Luego crecerá y se reducirá automáticamente verticalmente para adaptarse a su contenido.
mate
Tuve que establecer la propiedad preferidaMaxLayoutWidth + fijar el ancho de la etiqueta. Funciona como un encanto :)
MartinMoizard
preferredMaxLayoutWidth y / o fijar los lados izquierdo y derecho no es absoluto. Las prioridades de compresión y compresión de contenido siempre funcionarán siempre que sus prioridades sean más altas que el resto de las restricciones.
Eric Alford
2
^ Y finalmente descubrí por qué, vale la pena mencionar, cuando tienes la celda pegada en la parte inferior de la vista, debes establecer la prioridad de la restricción "inferior" en menos de 1000. La puse en 750 y Todo funciona perfectamente. Supongo que redujo la vista.
Mathijs Segers
65

En iOS 6, utilizando el diseño automático, si se fijan los lados de un UILabel (o anchura) y la parte superior, será automáticamente aumentar y reducir verticalmente para adaptarse a su contenido, con ningún código en absoluto y no ensuciar con su resistencia a la compresión o lo que sea. Es muy simple.

En casos más complejos, solo configure las etiquetas preferredMaxLayoutWidth.

De cualquier manera, lo correcto sucede automáticamente.

mate
fuente
12
También debe asegurarse de establecer numberOfLines en 0, de lo contrario no obtendrá el ajuste.
devongovett
2
Esto no fue suficiente para mí. También necesitaba el punto 2 de la respuesta de @ Mark.
Danyal Aytekin
¿También es posible hacer que la etiqueta crezca automáticamente en varias líneas sin escribir códigos?
Noor
Esto es mucho menos complicado que la respuesta aceptada. Hice dos ejemplos ( aquí y aquí ) que ilustran esta respuesta.
Suragch 01 de
@Suragch Usando restricciones esto funciona. Pero no funciona para el mismo código en Playground . En el patio de juegos debe tenerlabel.sizeToFit()
Honey
42

Aunque la pregunta establece programáticamente, habiendo encontrado el mismo problema y prefiriendo trabajar en Interface Builder, pensé que podría ser útil agregar a las respuestas existentes con una solución de Interface Builder.

Lo primero es olvidar sizeToFit. El diseño automático se encargará de esto en su nombre en función del tamaño del contenido intrínseco.

Por lo tanto, el problema es, ¿cómo hacer que una etiqueta se ajuste a su contenido con Auto Layout? Específicamente, porque la pregunta lo menciona, la altura. Tenga en cuenta que los mismos principios se aplican al ancho.

Comencemos con un ejemplo de UILabel que tiene una altura establecida en 41px de altura:

ingrese la descripción de la imagen aquí

Como puede ver en la captura de pantalla de arriba, "This is my text"tiene relleno arriba y abajo. Eso es un relleno entre la altura de UILabel, y su contenido, el texto.

Si ejecutamos la aplicación en el simulador, efectivamente, vemos lo mismo:

ingrese la descripción de la imagen aquí

Ahora, seleccionemos UILabel en Interface Builder y echemos un vistazo a la configuración predeterminada en el inspector de tamaño:

ingrese la descripción de la imagen aquí

Tenga en cuenta la restricción resaltada arriba. Esa es la prioridad de contenido que abraza . Como lo describe Erica Sadun en el excelente diseño automático de iOS desmitificado , esto es:

la forma en que una vista prefiere evitar el relleno adicional alrededor de su contenido principal

Para nosotros, con UILabel, el contenido central es el texto.

Aquí llegamos al corazón de este escenario básico. Le hemos dado a nuestra etiqueta de texto dos restricciones. Ellos entran en conflicto. Uno dice "la altura debe ser igual a 41 píxeles de alto" . El otro dice "abrace la vista a su contenido para que no tengamos ningún relleno adicional" . En nuestro caso, abrace la vista a su texto para que no tengamos ningún relleno adicional.

Ahora, con Auto Layout, con dos instrucciones diferentes que dicen hacer cosas diferentes, el tiempo de ejecución tiene que elegir uno u otro. No puede hacer las dos cosas. El UILabel no puede tener 41 píxeles de alto y no tiene relleno.

La forma en que esto se resuelve es especificando prioridad. Una instrucción debe tener mayor prioridad que la otra. Si ambas instrucciones dicen cosas diferentes y tienen la misma prioridad, se producirá una excepción.

Así que vamos a intentarlo. Mi restricción de altura tiene una prioridad de 1000 , que es obligatoria . La altura de abrazo del contenido es 250 , que es débil . ¿Qué sucede si reducimos la prioridad de restricción de altura a 249 ?

ingrese la descripción de la imagen aquí

Ahora podemos ver que la magia comienza a suceder. Probemos en el sim:

ingrese la descripción de la imagen aquí

¡Increíble! Contenido abrazado logrado. Solo porque la prioridad de altura 249 es menor que el contenido que abarca la prioridad 250 . Básicamente, digo que "la altura que especifico aquí es menos importante que la que especifiqué para el contenido abrazado" . Entonces, el contenido que abraza gana.

En pocas palabras, lograr que la etiqueta se ajuste al texto puede ser tan simple como especificar la restricción de altura o anchura y establecer correctamente esa prioridad en asociación con la restricción de prioridad de contenido de ese eje.

¡Saldrá haciendo el equivalente de ancho como ejercicio para el lector!

Max MacLeod
fuente
3
Muchas gracias por tu gran explicación! Finalmente entiendo la prioridad de abrazar contenido.
kernix
¿Podría explicar también qué es la prioridad de resistencia a la compresión de contenido? ¡Gracias!
kernix
2
Excelente respuesta Max! ¿Hay alguna forma de limitar el número de líneas que funcionan de esa manera?
rafaeljuzo
7

Notado en IOS7 sizeToFit tampoco funcionaba, tal vez la solución también pueda ayudarlo

[textView sizeToFit];
[textView layoutIfNeeded];
Nick Wood
fuente
1
@Rambatino Esto me funciona. Agregue layoutIfNeededcuando lo necesite setNeedsUpdateConstraints. Pero la situación puede ser un poco diferente.
Gon
Esto funcionó mejor para mí cuando hice un popover. Sin embargo, primero tenía que hacer el diseño si era necesario
Jason el
4

Otra opción para garantizar que el ancho máximo de la etiqueta preferido se mantenga sincronizado con el ancho de la etiqueta:

#import "MyLabel.h"

@implementation MyLabel

-(void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    // This appears to be needed for iOS 6 which doesn't seem to keep
    // label preferredMaxLayoutWidth in sync with its width, which 
    // means the label won't grow vertically to encompass its text if 
    // the label's width constraint changes.
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

@end
Monte Hurd
fuente
4

Siento que debería contribuir, ya que me llevó un tiempo encontrar la solución correcta:

  • El objetivo es dejar que Auto Layout haga su trabajo sin siquiera llamar a sizeToFit (), haremos esto especificando las restricciones correctas:
  • Especifique las restricciones de espacio superior, inferior y de inicio / final en su UILabel
  • Establezca la propiedad de número de líneas en 0
  • Incremente la prioridad de abrazo de contenido a 1000
  • Reduzca la prioridad de resistencia de compresión de contenido a 500
  • En su restricción de contenedor inferior, reduzca la prioridad a 500

Básicamente, lo que sucede es que le dice a su UILabel que, aunque tiene una restricción de altura fija, puede romper la restricción para hacerse más pequeña para abarcar el contenido (si tiene una sola línea, por ejemplo), pero no puede romper la restricción para hacerlo más grande.

Arturo
fuente
3

En mi caso, estaba creando una subclase UIView que contenía un UILabel (de longitud desconocida). En iOS7, el código era sencillo: establezca las restricciones, no se preocupe por el contenido o la resistencia a la compresión, y todo funcionó como se esperaba.

Pero en iOS6, el UILabel siempre se recortaba en una sola línea. Ninguna de las respuestas anteriores funcionó para mí. Se ignoraron los ajustes de contenido y resistencia de compresión. La única solución que evitó el recorte fue incluir un ancho de banda máximo preferido en la etiqueta. Pero no sabía a qué establecer el ancho preferido, ya que el tamaño de su vista principal era desconocido (de hecho, estaría definido por el contenido).

Finalmente encontré la solución aquí . Como estaba trabajando en una vista personalizada, podría agregar el siguiente método para establecer el ancho de diseño preferido después de que las restricciones se hubieran calculado una vez y luego volver a calcularlas:

- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}
sumizome
fuente
Este fue el escenario exacto que tuve ... a veces es bueno saber que no estás solo.
daveMac
Adam, estoy luchando con lo que dijiste que funcionó perfectamente en iOS7. Tengo una celda personalizada con una uiview y una uilabel. Puedo hacer que la etiqueta se ajuste al contenido, pero la vista no, sin importar las restricciones que le ponga. Como hiciste que funcionara? Juro que probé todo pero no toma la altura de la etiqueta
denikov
3

Agregué UILabelprogramáticamente y en mi caso eso fue suficiente:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
Mixel
fuente
2

He resuelto con xCode6 poniendo "Ancho preferido" en Automático y fije la parte superior de la etiqueta, al principio y al final ingrese la descripción de la imagen aquí

Kazzar
fuente
0
UIFont *customFont = myLabel.font;
CGSize size = [trackerStr sizeWithFont:customFont
                             constrainedToSize:myLabel.frame.size // the size here should be the maximum size you want give to the label
                                 lineBreakMode:UILineBreakModeWordWrap];
float numberOfLines = size.height / customFont.lineHeight;
myLabel.numberOfLines = numberOfLines;
myLabel.frame = CGRectMake(258, 18, 224, (numberOfLines * customFont.lineHeight));
Swati
fuente
0

Me encontré con este problema también con una subclase de UIView que contiene un UILabel como uno de sus elementos internos. Incluso con la distribución automática y probando todas las soluciones recomendadas, la etiqueta simplemente no ajustaría su altura al texto. En mi caso, solo quiero que la etiqueta sea una línea.

De todos modos, lo que funcionó para mí fue agregar una restricción de altura requerida para UILabel y establecerla manualmente en la altura correcta cuando se llama intrinsicContentSize. Si no tiene el UILabel contenido en otra UIView, puede intentar subclasificar UILabel y proporcionar una implementación similar configurando primero la restricción de altura y luego regresando
[super instrinsicContentSize]; en lugar de [self.containerview intrinsiceContentSize]; como hago a continuación, que es específico de mi subclase UIView.

- (CGSize)intrinsicContentSize
{
    CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
    self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
    return [self.containerView intrinsicContentSize];

}

Funciona perfectamente ahora en iOS 7 y iOS 8.

n8tr
fuente
Vale la pena señalar que si ha configurado su vista correctamente (es decir, sus restricciones son correctas y ha configurado todo durante las etapas correctas en el ciclo de vida de la vista), nunca debería tener que anular intrinsicContentSize. El tiempo de ejecución debería poder calcular esto en función de las restricciones de configuración correctas.
John Rogers
En teoría, eso debería ser correcto, pero no tenemos acceso a todas las API e implementaciones privadas de Apple y no son infalibles y tienen errores en su código.
n8tr
... pero [super intrinsicContentSize] debería encargarse de eso, es lo que estoy diciendo. Si ha implementado su diseño automático correctamente.
John Rogers
0

Una solución que funcionó para mí; Si su UILabel tiene un ancho fijo, cambie la restricción de constant =a constant <=en su archivo de interfaz

ingrese la descripción de la imagen aquí

Alex Brown
fuente
-1

En mi caso, cuando uso las etiquetas en un UITableViewCell, la etiqueta cambiaría de tamaño pero la altura superaría la altura de la celda de la tabla. Esto es lo que funcionó para mí. Lo hice de acuerdo con Max MacLeod, y luego me aseguré de que la altura de la celda estuviera establecida en UITableViewAutomaticDimension.

Puede agregar esto en su init o awakeFromNib,

self.tableView.rowHeight = UITableViewAutomaticDimension.  

En el guión gráfico, seleccione la celda, abra el inspector de tamaño y asegúrese de que la altura de la fila esté establecida en "Predeterminado" desmarcando la casilla de verificación "Personalizado".

Hay un problema en 8.0 que también requiere que se establezca en el código.

Maria
fuente