iOS AutoLayout multi-line UILabel
La siguiente pregunta es una especie de continuación de esta:
IOS: Etiqueta 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 el diseño automático pueda saber cómo mostrarlo correctamente. UILabel es solo un ejemplo de una situación en la que una vista no puede por sí misma saber qué tamaño necesita para mostrarse. Depende de qué anchura se proporciona.
Como mwhuss señaló, setPreferredMaxLayoutWidth hizo el truco de hacer que la etiqueta abarcara varias líneas. Pero esa no es la cuestión principal aquí. La pregunta es dónde y cuándo obtengo este valor width que envío como argumento a setPreferredMaxLayoutWidth .
Me las arreglé para hacer algo que se ve de fiar, así que corrígeme si me equivoco de alguna manera y dime por favor si sabes una mejor manera.
En el Uiview's
-(CGSize) intrinsicContentSize
I setPreferredMaxLayoutWidth para mis UILabels según self.marco.ancho.
UIViewController
-(void) viewDidLayoutSubviews
Es el primer método de devolución de llamada que sé donde subviews de la vista principal se nombran con sus marcos exactos que habitan en la pantalla. Desde el interior de ese método, entonces, opero en mis subviews, invalidando sus tamaños intrínsecos para que los UILabels se rompan en múltiples líneas basadas en el ancho que se les asignó.
9 answers
Parece molesto que una etiqueta UILabel no tenga por defecto su ancho para el ancho máximo de diseño preferido, si tiene restricciones que definen inequívocamente ese ancho para usted.
En casi todos los casos que he usado etiquetas bajo Diseño automático, 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.
Por lo tanto, para que esto suceda automáticamente, he utilizado una subclase UILabel, que anula setBounds:
. Toma, llama a la super implementación, entonces, si no es el caso ya, establezca el ancho de diseño máximo preferido para que sea el ancho de tamaño de los límites.
El énfasis es importante: establecer el diseño máximo preferido causa que se realice otro pase de diseño, por lo que puede terminar con un bucle infinito.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-05 16:18:09
Hay una respuesta a esta pregunta en objc.io en la sección "Tamaño de Contenido Intrínseco de Texto Multilínea" de Advanced Auto Layout Toolbox. Aquí está la información relevante:
El tamaño de contenido intrínseco de UILabel y NSTextField es ambiguo para texto multilínea. 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 máximo de línea para calcular el tamaño de contenido intrínseco.
Dado que generalmente no conocemos este valor de antemano, necesitamos tomar un enfoque de dos pasos para hacerlo bien. Primero dejamos que el Diseño automático haga su trabajo, y luego usamos el marco resultante en el pase de diseño para actualizar el ancho máximo preferido y volver a activar el diseño.
El código que dan para su uso dentro de un controlador de vista:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
[self.view layoutIfNeeded];
}
Echa un vistazo a su post, hay más información sobre por qué es necesario hacer el diseño dos veces.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-27 08:55:52
Update
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 errores en iOS 7 e iOS 8. https://github.com/nicksnyder/ios-cell-layout
Respuesta original
Esta es una solución completa que funciona para mí en iOS 7 y iOS 8
Objetivo C
@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
Swift
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()
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-17 15:27:29
Tuvimos una situación en la que un UILabel auto-layouted dentro de un UIScrollView se presentó bien en retrato, pero cuando se giró al paisaje, la altura del UILabel no se recalculó.
Encontramos que la respuesta de @jrturton arregló esto, presumiblemente porque ahora el preferredMaxLayoutWidth está establecido correctamente.
Aquí está el código que usamos. Simplemente configure la clase personalizada de Interface builder para que sea CVFixedWidthMultiLineLabel.
CVFixedWidthMultiLineLabel.h
@interface CVFixedWidthMultiLineLabel : UILabel
@end
CVFixedWidthMultiLineLabel.m
@implementation CVFixedWidthMultiLineLabel
// Fix for layout failure for multi-line text from
// http://stackoverflow.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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-16 13:21:59
Usando boundingRectWithSize
Resolví mi lucha con dos etiquetas multilínea en un legado UITableViewCell
que estaba usando "\n " como un salto de línea midiendo 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 era tan simple como:
CGFloat labelPreferredWidth = [self preferredMaxLayoutWidthForLabel:textLabel];
if (labelPreferredWidth > 0.0f) {
textLabel.preferredMaxLayoutWidth = labelPreferredWidth;
}
[textLabel layoutIfNeeded];
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-17 17:58:05
Como no se me permite 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
el preferredMaxLayoutWidth
siempre estaba 0
en updateViewConstraints
. Y, sin embargo, siempre tenía el valor deseado cuando se comprueba en setBounds:
. No logré saber CUÁNDO se estableció el preferredMaxLayoutWidth
correcto. Anulé setPreferredMaxLayoutWidth:
en la subclase UILabel
, pero nunca fue llamada.
Resumido, I:
- ...UILabel sublcassed
- ...y anular
setBounds:
a, si no está ya establecido , establezcapreferredMaxLayoutWidth
aCGRectGetWidth(bounds)
- ...llame
[super updateViewConstraints]
antes de lo siguiente - ...llame a
layoutIfNeeded
antes de usarpreferredMaxLayoutWidth
en el cálculo del tamaño de la etiqueta
EDIT: Esta solución solo parece funcionar, o ser necesaria, a veces. Acabo de tener un problema (iOS 7/8) donde la altura de la etiqueta no se calculó correctamente, ya que preferredMaxLayoutWidth
devolvió 0
después del diseño el proceso se había ejecutado una vez. Así que después de un poco de ensayo y error (y después de haber encontrado esta entrada de Blog ) cambié a usar UILabel
nuevamente y solo configuré las restricciones de diseño automático superior, inferior, izquierda y derecha. Y por cualquier razón, la altura de la etiqueta se estableció correctamente después de actualizar el texto.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-13 10:14:15
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 era visible en la interfaz de usuario y causó un "parpadeo visible", es decir, primero la etiqueta se renderizó con la altura de dos líneas, luego se volvió a renderizar con la altura de una sola línea.
Esto no era 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".
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-08 23:30:46
Una solución limpia es establecer rowcount = 0
y usar una propiedad para la restricción de altura de su etiqueta. Luego, después de establecer el contenido, 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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-26 23:20:24
En iOS 8, puede solucionar problemas de diseño de etiquetas multilínea en una celda llamando a cell.layoutIfNeeded()
después de desquejar y configurar la celda. La llamada es inofensiva en iOS 9.
Ver la respuesta de Nick Snyder. Esta solución fue tomada de su código en https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.swift.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-05 17:01:42