ios uilabel autolayout

iOS AutoLayout multi-line UILabel



(9)

La siguiente pregunta es una especie de continuación de esta:

iOS: UILabel multilínea en diseño automático

La idea principal es que cada vista debe indicar su tamaño "preferido" (intrínseco) para que AutoLayout pueda saber cómo mostrarlo correctamente. UILabel es solo un ejemplo de una situación en la que una vista no puede saber por sí misma qué tamaño necesita para visualizar. Depende de qué ancho se proporciona.

Como señaló mwhuss , setPreferredMaxLayoutWidth hizo el truco de hacer que la etiqueta abarque varias líneas. Pero esa no es la pregunta principal aquí. La pregunta es dónde y cuándo obtengo este valor de ancho que envío como argumento a setPreferredMaxLayoutWidth .

Logré hacer algo que parece legítimo, así que corrígeme si me equivoco de alguna manera y dime si sabes una mejor manera.

En el UIView''s

-(CGSize) intrinsicContentSize

Establecí PreferredMaxLayoutWidth para mis UILabels de acuerdo con self.frame.width.

UIViewController''s

-(void) viewDidLayoutSubviews

es el primer método de devolución de llamada que sé donde se asignan subvistas de la vista principal con sus marcos exactos que habitan en la pantalla. Desde dentro de ese método, yo, entonces, opero en mis subvistas, invalidando sus tamaños intrínsecos para que los UILabels se dividan en líneas múltiples en función del ancho que se les asignó.


Usar boundingRectWithSize

Resolví mi lucha con dos etiquetas multilínea en una UITableViewCell heredada que estaba usando "/ n" como un salto de línea al medir el ancho deseado de esta manera:

- (CGFloat)preferredMaxLayoutWidthForLabel:(UILabel *)label { CGFloat preferredMaxLayoutWidth = 0.0f; NSString *text = label.text; UIFont *font = label.font; if (font != nil) { NSMutableParagraphStyle *mutableParagraphStyle = [[NSMutableParagraphStyle alloc] init]; mutableParagraphStyle.lineBreakMode = NSLineBreakByWordWrapping; NSDictionary *attributes = @{NSFontAttributeName: font, NSParagraphStyleAttributeName: [mutableParagraphStyle copy]}; CGRect boundingRect = [text boundingRectWithSize:CGSizeZero options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; preferredMaxLayoutWidth = ceilf(boundingRect.size.width); NSLog(@"Preferred max layout width for %@ is %0.0f", text, preferredMaxLayoutWidth); } return preferredMaxLayoutWidth; }

Entonces, llamar al método fue tan simple como:

CGFloat labelPreferredWidth = [self preferredMaxLayoutWidthForLabel:textLabel]; if (labelPreferredWidth > 0.0f) { textLabel.preferredMaxLayoutWidth = labelPreferredWidth; } [textLabel layoutIfNeeded];


Parece molesto que un UILabel no adopte de manera predeterminada su ancho para el ancho de diseño máximo preferido, si tiene restricciones que definan inequívocamente ese ancho para usted.

En casi todos los casos que utilicé etiquetas en Autolayout, el ancho máximo de diseño preferido ha sido el ancho real de la etiqueta, una vez que se ha realizado el resto de mi diseño.

Entonces, para que esto suceda automáticamente, he usado una subclase UILabel, que anula setBounds: Aquí, llame a la implementación súper, entonces, si ya no es el caso , configure el ancho de diseño máximo preferido para que sea el ancho del tamaño de los límites.

El énfasis es importante: configurar el diseño máximo preferido hace que se realice otra pasada de diseño, por lo que puede terminar con un ciclo infinito.


Como lo sugirió otra respuesta, traté de anular viewDidLayoutSubviews :

- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40; [self.view layoutIfNeeded]; }

Esto funcionó, pero fue visible en la interfaz de usuario y causó un "parpadeo visible", es decir, primero la etiqueta se procesó con la altura de dos líneas, luego se volvió a renderizar con la altura de una sola línea.

Esto no fue aceptable para mí.

Encontré entonces una mejor solución anulando updateViewConstraints :

-(void)updateViewConstraints { [super updateViewConstraints]; // Multiline-Labels and Autolayout do not work well together: // In landscape mode the width is still "portrait" when the label determines the count of lines // Therefore the preferredMaxLayoutWidth must be set _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40; }

Esta fue la mejor solución para mí, porque no causó el "parpadeo visual".


Tuvimos una situación en la que UILabel con un trazado automático dentro de UIScrollView se diseñó bien en vertical, pero cuando se rotó a horizontal, la altura del UILabel no se volvió a calcular.

Descubrimos que la respuesta de @jrturton solucionó esto, presumiblemente porque ahora el preferredMaxLayoutWidth está configurado correctamente.

Aquí está el código que usamos. Simplemente configure la clase personalizada del constructor Interfaz para que sea CVFixedWidthMultiLineLabel .

CVFixedWidthMultiLineLabel.h

@interface CVFixedWidthMultiLineLabel : UILabel @end

CVFixedWidthMultiLineLabel.m

@implementation CVFixedWidthMultiLineLabel // Fix for layout failure for multi-line text from // http://.com/questions/17491376/ios-autolayout-multi-line-uilabel - (void) setBounds:(CGRect)bounds { [super setBounds:bounds]; if (bounds.size.width != self.preferredMaxLayoutWidth) { self.preferredMaxLayoutWidth = self.bounds.size.width; } } @end


Una solución limpia es establecer rowcount = 0 y usar una propiedad para la restricción de altura de su etiqueta. Luego, una vez que el contenido está configurado, llame

CGSize sizeThatFitsLabel = [_subtitleLabel sizeThatFits:CGSizeMake(_subtitleLabel.frame.size.width, MAXFLOAT)]; _subtitleLabelHeightConstraint.constant = ceilf(sizeThatFitsLabel.height);

-(void) updateViewConstraints tiene un problema desde iOS 7.1.


Hay una respuesta a esta pregunta en objc.io en la sección "Tamaño de contenido intrínseco de texto de varias líneas" de Advanced Auto Layout Toolbox . Aquí está la información relevante:

El tamaño del contenido intrínseco de UILabel y NSTextField es ambiguo para el texto de varias líneas. La altura del texto depende del ancho de las líneas, que aún no se ha determinado al resolver las restricciones. Para resolver este problema, ambas clases tienen una nueva propiedad llamada preferredMaxLayoutWidth, que especifica el ancho de línea máximo para calcular el tamaño de contenido intrínseco.

Dado que generalmente no conocemos este valor por adelantado, tenemos que tomar un enfoque de dos pasos para hacerlo bien. Primero permitimos que Auto Layout haga su trabajo, y luego usamos el marco resultante en el pase de diseño para actualizar el ancho máximo preferido y disparar el diseño nuevamente.

El código que dan para usar dentro de un controlador de vista:

- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width; [self.view layoutIfNeeded]; }

Eche un vistazo a su publicación, hay más información sobre por qué es necesario hacer el diseño dos veces.


Como no puedo agregar un comentario, estoy obligado a agregarlo como respuesta. La versión de jrturton solo funcionó para mí si llamo layoutIfNeeded en updateViewConstraints antes de obtener el preferredMaxLayoutWidth de la etiqueta en cuestión. Sin la llamada a layoutIfNeeded preferredMaxLayoutWidth siempre era 0 en updateViewConstraints . Y, sin embargo, siempre tenía el valor deseado cuando estaba marcado en setBounds: No pude conocer CUÁNDO se configuró el preferredMaxLayoutWidth . setPreferredMaxLayoutWidth: en la subclase UILabel , pero nunca se llamó.

Resumido, yo:

  • ... sublubicado UILabel
  • ... y anula setBounds: a, si no está ya establecido , establezca preferredMaxLayoutWidth en CGRectGetWidth(bounds)
  • ... llame a [super updateViewConstraints] antes de lo siguiente
  • ... layoutIfNeeded antes de obtener preferredMaxLayoutWidth para usar en el cálculo del tamaño de la etiqueta

EDITAR: Esta solución parece funcionar, o ser necesaria, a veces. Acabo de tener un problema (iOS 7/8) en el que la altura de la etiqueta no se calculó correctamente, ya que preferredMaxLayoutWidth devolvió 0 después de que el proceso de diseño se hubiera ejecutado una vez. Así que después de un poco de prueba y error (y de haber encontrado esta entrada en el blog ) UILabel a usar UILabel nuevamente y simplemente establecí restricciones de diseño automático superior, inferior, izquierda y derecha. Y por cualquier motivo, la altura de la etiqueta se estableció correctamente después de actualizar el texto.



Actualizar

Mi respuesta original parece ser útil, así que la he dejado intacta a continuación, sin embargo, en mis propios proyectos he encontrado una solución más confiable que funciona alrededor de los errores en iOS 7 e iOS 8. https://github.com/nicksnyder/ios -diseño de la celda

Respuesta original

Esta es una solución completa que funciona para mí en iOS 7 e iOS 8

C objetivo

@implementation AutoLabel - (void)setBounds:(CGRect)bounds { if (bounds.size.width != self.bounds.size.width) { [self setNeedsUpdateConstraints]; } [super setBounds:bounds]; } - (void)updateConstraints { if (self.preferredMaxLayoutWidth != self.bounds.size.width) { self.preferredMaxLayoutWidth = self.bounds.size.width; } [super updateConstraints]; } @end

Rápido

import Foundation class EPKAutoLabel: UILabel { override var bounds: CGRect { didSet { if (bounds.size.width != oldValue.size.width) { self.setNeedsUpdateConstraints(); } } } override func updateConstraints() { if(self.preferredMaxLayoutWidth != self.bounds.size.width) { self.preferredMaxLayoutWidth = self.bounds.size.width } super.updateConstraints() } }