ios - AutoLayout multilínea UILabel cortando un texto
objective-c uistoryboard (4)
Encontré una respuesta aún mejor después de leer esto: http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights
creando una subclase de UILabel
para anular layoutSubviews
así:
- (void)layoutSubviews
{
[super layoutSubviews];
self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
[super layoutSubviews];
}
Esto asegura que el preferredMaxLayoutWidth
sea siempre correcto.
Actualizar:
Después de varios lanzamientos más de iOS y probando más casos de uso, encontré que lo anterior no es suficiente para cada caso. A partir de iOS 8 (creo), en algunas circunstancias, solo se didSet
bounds
antes de que se cargara la pantalla.
En iOS 9, muy recientemente, encontré otro problema al usar un UIVIewController
modal con un UITableView
, que se layoutSubviews
tanto las layoutSubviews
como los set bounds
después de heightForRowAtIndexPath
. Después de mucha depuración, la única solución era anular el set frame
.
El código de abajo ahora parece ser necesario para garantizar que funcione en todos los iOS, controladores, tamaños de pantalla, etc.
override public func layoutSubviews()
{
super.layoutSubviews()
self.preferredMaxLayoutWidth = self.bounds.width
super.layoutSubviews()
}
override public var bounds: CGRect
{
didSet
{
self.preferredMaxLayoutWidth = self.bounds.width
}
}
override public var frame: CGRect
{
didSet
{
self.preferredMaxLayoutWidth = self.frame.width
}
}
Estoy tratando de aprender el diseño automático, finalmente pensé que estaba teniendo problemas cuando esto sucedió. Estoy jugando con prototipos de células y etiquetas. Estoy tratando de tener un
- Título,
titleLbl
- cuadro azul en la imagen - Dinero,
moneyLbl
- cuadro verde en la imagen - Subtítulo / Descripción,
subTitleLbl
- cuadro rojo en la imagen
Hasta ahora tengo esto:
Lo que quiero lograr es:
- El cuadro verde siempre debe mostrarse en 1 línea completamente
- El valor dentro del cuadro verde debe estar centrado según el cuadro azul
- El cuadro azul tomará el espacio restante (-8px para la restricción horizontal entre los 2) y se mostrará en varias líneas si es necesario
- El cuadro rojo debe estar debajo y debe mostrar tantas líneas como sea necesario.
Como puede ver, casi todo funciona, con la excepción del ejemplo de la última fila. He tratado de investigar esto y de lo que puedo recopilar es algo que tiene que ver con el tamaño del contenido intrínseco. Muchas publicaciones en línea recomiendan establecer setPreferredMaxLayoutWidth
, he hecho esto en varios lugares para el titleLbl
y no ha tenido ningún efecto. Solo cuando lo codifiqué a 180
obtuve los resultados, pero también añadí espacios en blanco / relleno a los otros cuadros azules en la parte superior / debajo del texto, lo que aumentó considerablemente el espacio entre los cuadros rojo / azul.
Aquí están mis limitaciones:
Título:
Dinero:
Subtitular:
Según las pruebas que he ejecutado con otras muestras de texto, parece que utiliza el ancho como se muestra en el guión gráfico, pero cualquier intento de corregirlo no funciona.
He creado un ivar de la celda y lo he usado para crear la altura de la celda:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
[sizingCellTitleSubMoney.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
[sizingCellTitleSubMoney.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
[sizingCellTitleSubMoney.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];
[sizingCellTitleSubMoney layoutIfNeeded];
CGSize size = [sizingCellTitleSubMoney.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height+1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MATitleSubtitleMoneyTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifierTitleSubTitleMoney];
if(!cell)
{
cell = [[MATitleSubtitleMoneyTableViewCell alloc] init];
}
cell.titleLbl.layer.borderColor = [UIColor blueColor].CGColor;
cell.titleLbl.layer.borderWidth = 1;
cell.subtitleLbl.layer.borderColor = [UIColor redColor].CGColor;
cell.subtitleLbl.layer.borderWidth = 1;
cell.moneyLbl.layer.borderColor = [UIColor greenColor].CGColor;
cell.moneyLbl.layer.borderWidth = 1;
[cell.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
[cell.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
[cell.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];
return cell;
}
He intentado configurar setPreferredMaxLayoutWidth
dentro de las subvistas de diseño de la celda:
- (void)layoutSubviews
{
[super layoutSubviews];
NSLog(@"Money lbl: %@", NSStringFromCGRect(self.moneyLbl.frame));
NSLog(@"prefferredMaxSize: %f /n/n", self.moneyLbl.frame.origin.x-8);
[self.titleLbl setPreferredMaxLayoutWidth: self.moneyLbl.frame.origin.x-8];
}
Iniciar sesión:
2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] Money lbl: {{209, 20}, {91, 61}}
2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] prefferredMaxSize: 201.000000
Que es lo que esperaría, ya que hay 8px entre los 2 elementos y la consola lo verifica. Funciona perfectamente en la primera fila, sin importar cuánto texto agregue al cuadro azul, que es el mismo que el guión gráfico, pero cualquier aumento en el cuadro verde elimina el cálculo de ancho. He intentado configurar esto en el tableView:willDisplayCell
y el tableView:heightForRowAtIndexPath:
también en base a las respuestas de otras preguntas. Pero no importa lo que simplemente no tiene diseño.
Cualquier ayuda, ideas o comentarios serían muy apreciados.
No estoy seguro de entenderlo bien y mi solución está muy lejos de ser la mejor, pero aquí está:
Agregué una restricción de altura en la etiqueta que se cortó, conecté esa restricción a la salida de la celda. He anulado el método layoutSubview:
- (void) layoutSubviews {
[super layoutSubviews];
[self.badLabel setNeedsLayout];
[self.badLabel layoutIfNeeded];
self.badLabelHeightConstraint.constant = self.badLabel.intrinsicContentSize.height;
}
y viola! Por favor, intente ese método y dime los resultados, gracias.
Omg, acabo de tener el mismo problema. La respuesta de @Simon McLoughlin no me ayudó. Pero
titleLabel.adjustsFontSizeToFitWidth = true
Hizo una magia! Funciona, no sé por qué, pero lo hace. Y sí, su etiqueta debe tener un valor explícito de preferredMaxLayoutWidth.
Pasé un poco de tiempo envolviendo la celda, con este aspecto:
+--------------------------------------------------+
| This is my cell.contentView |
| +------+ +-----------------+ +--------+ +------+ |
| | | | multiline | | title2 | | | |
| | img | +-----------------+ +--------+ | img | |
| | | +-----------------+ +--------+ | | |
| | | | title3 | | title4 | | | |
| +------+ +-----------------+ +--------+ +------+ |
| |
+--------------------------------------------------+
Después de muchos intentos, vine con una solución sobre cómo usar un prototipo de célula que realmente funciona.
El principal problema era que mis etiquetas podían estar o no estar allí. Cada elemento debe ser opcional.
En primer lugar, nuestra celda ''prototipo'' solo debe diseñar cosas cuando no estamos en un entorno "real".
Por lo tanto, creé la bandera ''heightCalculationInProgress''.
Cuando alguien (dataSource de tableView) solicita calcular la altura, proporcionamos datos a la celda prototipo utilizando el bloque: Y luego le pedimos a nuestra célula prototipo que diseñe el contenido y extraiga la altura de la misma. Sencillo.
Para evitar el error de iOS7 con layoutSubview recursion hay una variable "layoutingLock"
- (CGFloat)heightWithSetupBlock:(nonnull void (^)(__kindof UITableViewCell *_Nonnull cell))block {
block(self);
self.heightCalculationInProgress = YES;
[self setNeedsLayout];
[self layoutIfNeeded];
self.layoutingLock = NO;
self.heightCalculationInProgress = NO;
CGFloat calculatedHeight = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGFloat height = roundf(MAX(calculatedHeight, [[self class] defaultHeight]));
return height;
}
Este ''bloque'' desde el exterior podría verse así:
[prototypeCell heightWithSetupBlock:^(__kindof UITableViewCell * _Nonnull cell) {
@strongify(self);
cell.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 0);
cell.dataObject = dataObject;
cell.multilineLabel.text = @"Long text";
// Include any data here
}];
Y ahora empieza la parte más interesante. Disposición:
- (void)layoutSubviews {
if(self.layoutingLock){
return;
}
// We don''t want to do this layouting things in ''real'' cells
if (self.heightCalculationInProgress) {
// Because of:
// Support for constraint-based layout (auto layout)
// If nonzero, this is used when determining -intrinsicContentSize for multiline labels
// We''re actually resetting internal constraints here. And then we could reuse this cell with new data to layout it correctly
_multilineLabel.preferredMaxLayoutWidth = 0.f;
_title2Label.preferredMaxLayoutWidth = 0.f;
_title3Label.preferredMaxLayoutWidth = 0.f;
_title4Label.preferredMaxLayoutWidth = 0.f;
}
[super layoutSubviews];
// We don''t want to do this layouting things in ''real'' cells
if (self.heightCalculationInProgress) {
// Make sure the contentView does a layout pass here so that its subviews have their frames set, which we
// need to use to set the preferredMaxLayoutWidth below.
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
// Set the preferredMaxLayoutWidth of the mutli-line bodyLabel based on the evaluated width of the label''s frame,
// as this will allow the text to wrap correctly, and as a result allow the label to take on the correct height.
_multilineLabel.preferredMaxLayoutWidth = CGRectGetWidth(_multilineLabel.frame);
_title2Label.preferredMaxLayoutWidth = CGRectGetWidth(_title2Label.frame);
_title3Label.preferredMaxLayoutWidth = CGRectGetWidth(title3Label.frame);
_title4Label.preferredMaxLayoutWidth = CGRectGetWidth(_title4Label.frame);
}
if (self.heightCalculationInProgress) {
self.layoutingLock = YES;
}
}