ios - uitableviewcell - Las alturas de celda dinámica UITableView solo se corrigen después de un desplazamiento
uitableview with multiple custom cells swift (22)
Tengo una UITableView
con una UITableViewCell
personalizada definida en un guión gráfico mediante el diseño automático. La celda tiene varios UILabels
multilínea.
El UITableView
parece calcular correctamente las alturas de las celdas, pero para las primeras celdas esa altura no se divide correctamente entre las etiquetas. Después de desplazarse un poco, todo funciona como se esperaba (incluso las celdas que inicialmente eran incorrectas).
- (void)viewDidLoad {
[super viewDidLoad]
// ...
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
// ...
// Set label.text for variable length string.
return cell;
}
¿Hay algo que pueda estar perdiendo, que está causando que el diseño automático no sea capaz de hacer su trabajo las primeras veces?
Creé un proyecto de muestra que demuestra este comportamiento.
Agregar [cell layoutIfNeeded]
en cellForRowAtIndexPath
no funciona para celdas que se desplazan inicialmente fuera de vista.
Tampoco lo hace prefabricando con [cell setNeedsLayout]
.
Todavía tiene que desplazar ciertas celdas y volver a la vista para que cambien de tamaño correctamente.
Esto es bastante frustrante ya que la mayoría de los desarrolladores tienen las células Dynamic Type, AutoLayout y Self-Sizing funcionando correctamente, a excepción de este caso molesto. Este error afecta a todos mis controladores de mesa "más altos".
Agregue una restricción para todo el contenido dentro de una celda personalizada de la vista de tabla, luego estime la altura de la fila de la vista de tabla y configure la altura de la fila en la dimensión automática con una carga viewdid:
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 70
tableView.rowHeight = UITableViewAutomaticDimension
}
Para solucionar ese problema de carga inicial, aplique el método layoutIfNeeded en una celda de vista de tabla personalizada:
class CustomTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
self.layoutIfNeeded()
// Initialization code
}
}
El problema es que las celdas iniciales se cargan antes de que tengamos una altura de fila válida. La solución consiste en forzar la recarga de una tabla cuando aparece la vista.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.tableView reloadData];
}
En Swift 3. Tuve que llamar a self.layoutIfNeeded () cada vez que actualicé el texto de la celda reutilizable.
import UIKit
import SnapKit
class CommentTableViewCell: UITableViewCell {
static let reuseIdentifier = "CommentTableViewCell"
var comment: Comment! {
didSet {
textLbl.attributedText = comment.attributedTextToDisplay()
self.layoutIfNeeded() //This is a fix to make propper automatic dimentions (height).
}
}
internal var textLbl = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
if textLbl.superview == nil {
textLbl.numberOfLines = 0
textLbl.lineBreakMode = .byWordWrapping
self.contentView.addSubview(textLbl)
textLbl.snp.makeConstraints({ (make) in
make.left.equalTo(contentView.snp.left).inset(10)
make.right.equalTo(contentView.snp.right).inset(10)
make.top.equalTo(contentView.snp.top).inset(10)
make.bottom.equalTo(contentView.snp.bottom).inset(10)
})
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let comment = comments[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CommentTableViewCell.reuseIdentifier, for: indexPath) as! CommentTableViewCell
cell.selectionStyle = .none
cell.comment = comment
return cell
}
commentsTableView.rowHeight = UITableViewAutomaticDimension
commentsTableView.estimatedRowHeight = 140
En mi caso, el problema con la altura de la celda tiene lugar después de que se carga la vista de tabla inicial, y se lleva a cabo una acción del usuario (tocando un botón en una celda que tiene el efecto de cambiar la altura de la celda). No he podido hacer que la celda cambie su altura a menos que lo haga:
[self.tableView reloadData];
lo intenté
[cell layoutIfNeeded];
pero eso no funcionó.
En mi caso, estaba actualizando en otro ciclo. Así que la altura de tableViewCell se actualizó después de establecer etiquetaTexto. Borré el bloque asíncrono.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:Identifier, for: indexPath)
// Check your cycle if update cycle is same or not
// DispatchQueue.main.async {
cell.label.text = nil
// }
}
En mi caso, la última línea de UILabel se truncó cuando se mostró la celda por primera vez. Sucedió bastante al azar y la única forma de ajustarlo correctamente era desplazar la celda de la vista y recuperarla. Intenté todas las soluciones posibles mostradas hasta el momento (layoutIfNeeded..reloadData) pero nada funcionó para mí. El truco fue establecer "Autoshrink" en Minimuum Font Scale (0.5 para mí). Darle una oportunidad
En mi caso, una vista de pila en la celda estaba causando el problema. Es un error aparentemente. Una vez que lo eliminé, el problema fue resuelto.
Establecer preferredMaxLayoutWidth
ayuda en mi caso. yo añadí
cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width
en mi código
Consulte también El texto de una sola línea toma dos líneas en UILabel y http://openradar.appspot.com/17799811 .
Esto funcionó para mí cuando otras soluciones similares no lo hicieron:
override func didMoveToSuperview() {
super.didMoveToSuperview()
layoutIfNeeded()
}
Esto parece un error real, ya que estoy muy familiarizado con AutoLayout y con el uso de UITableViewAutomaticDimension; sin embargo, de vez en cuando me encuentro con este problema. Me alegra que finalmente encontré algo que funciona como una solución alternativa.
He intentado la mayoría de las respuestas a esta pregunta y no pude conseguir que ninguna de ellas funcione. La única solución funcional que encontré fue agregar lo siguiente a mi subclase UITableViewController
:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIView.performWithoutAnimation {
tableView.beginUpdates()
tableView.endUpdates()
}
}
Se UIView.performWithoutAnimation
llamada UIView.performWithoutAnimation
, de lo contrario verá la animación de vista de tabla normal cuando se carga el controlador de vista.
Intenté todas las soluciones en esta página, pero al desmarcar las clases de tamaño de uso, al volver a comprobarlo, resolví mi problema.
Editar: Desmarcar clases de tamaño causa muchos problemas en el guión gráfico, así que probé con otra solución. ViewDidLoad
ViewWillAppear
métodos ViewDidLoad
y ViewWillAppear
del controlador de vista. Esto resolvió mi problema.
Me encontré con este problema y lo solucioné moviendo mi código de inicialización de vista / etiqueta FROM tableView(willDisplay cell:)
TO tableView(cellForRowAt:)
.
Ninguna de las soluciones anteriores funcionó, pero sí la siguiente combinación de sugerencias.
Tuve que agregar lo siguiente en viewDidLoad ().
DispatchQueue.main.async {
self.tableView.reloadData()
self.tableView.setNeedsLayout()
self.tableView.layoutIfNeeded()
self.tableView.reloadData()
}
La combinación anterior de reloadData, setNeedsLayout y layoutIfNeeded funcionó, pero no ninguna otra. Sin embargo, podría ser específico para las celdas del proyecto. Y sí, tuvo que invocar reloadData dos veces para que funcione.
Configure también lo siguiente en viewDidLoad
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight
En tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
cell.setNeedsLayout()
cell.layoutIfNeeded()
No sé si esto está claramente documentado o no, pero al agregar [cell layoutIfNeeded]
antes de devolver la celda se resuelve tu problema.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
NSUInteger n1 = firstLabelWordCount[indexPath.row];
NSUInteger n2 = secondLabelWordCount[indexPath.row];
[cell setNumberOfWordsForFirstLabel:n1 secondLabel:n2];
[cell layoutIfNeeded]; // <- added
return cell;
}
Solo asegúrate de no configurar el texto de la etiqueta en el método delegado ''willdisplaycell'' de la vista de tabla. Establezca el texto de etiqueta en el método de delegado ''cellForRowAtindexPath'' para el cálculo dinámico de altura.
De nada :)
Tenía la misma experiencia en uno de mis proyectos.
¿Por qué sucede?
Celda diseñada en Storyboard con algo de ancho para algún dispositivo. Por ejemplo 400px. Por ejemplo, su etiqueta tiene el mismo ancho. Cuando se carga desde el guión gráfico, tiene un ancho de 400 píxeles.
Aquí hay un problema:
tableView:heightForRowAtIndexPath:
llamado antes del diseño de la celda son las subvistas.
Entonces calculó la altura para la etiqueta y la celda con un ancho de 400px. Pero se ejecuta en el dispositivo con pantalla, por ejemplo, 320px. Y esta altura calculada automáticamente es incorrecta. Solo porque el layoutSubviews
las layoutSubviews
ocurre solo después de tableView:heightForRowAtIndexPath:
incluso si configura preferredMaxLayoutWidth
para su etiqueta manualmente en layoutSubviews
esto no ayuda.
Mi solución:
1) Subclase UITableView
y anulación dequeueReusableCellWithIdentifier:forIndexPath:
Establezca el ancho de la celda igual al ancho de la tabla y forzar el diseño de la celda.
- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
CGRect cellFrame = cell.frame;
cellFrame.size.width = self.frame.size.width;
cell.frame = cellFrame;
[cell layoutIfNeeded];
return cell;
}
2) Subclase UITableViewCell
. Establezca preferredMaxLayoutWidth
manualmente para sus etiquetas en layoutSubviews
. También es necesario configurar manualmente contentView
, porque no se distribuye automáticamente después del cambio de marco de celda (no sé por qué, pero lo es)
- (void)layoutSubviews {
[super layoutSubviews];
[self.contentView layoutIfNeeded];
self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
Tengo el problema de cambiar el tamaño de la etiqueta, así que solo tengo que hacer
chatTextLabel.text = chatMessage.message chatTextLabel? .updateConstraints () después de configurar el texto
// código completo
func setContent() {
chatTextLabel.text = chatMessage.message
chatTextLabel?.updateConstraints()
let labelTextWidth = (chatTextLabel?.intrinsicContentSize().width) ?? 0
let labelTextHeight = chatTextLabel?.intrinsicContentSize().height
guard labelTextWidth < originWidth && labelTextHeight <= singleLineRowheight else {
trailingConstraint?.constant = trailingConstant
return
}
trailingConstraint?.constant = trailingConstant + (originWidth - labelTextWidth)
}
Tengo un problema similar, en la primera carga, la altura de la fila no se calculó, pero después de un desplazamiento o ir a otra pantalla y vuelvo a esta pantalla, se calculan las filas. En la primera carga, mis artículos se cargan desde Internet y, en la segunda carga, mis artículos se cargan primero desde Core Data y se vuelven a cargar desde Internet, y noté que la altura de las filas se calcula en la recarga desde Internet. Así que noté que cuando se llama a tableView.reloadData () durante la animación de segue (el mismo problema con push y segue actual), no se calculó la altura de la fila. Así que escondí la vista de tabla en la inicialización de la vista y puse un cargador de actividades para evitar un efecto desagradable para el usuario y llamé a tableView.reloadData después de 300ms y ahora el problema está resuelto. Creo que es un error de UIKit, pero esta solución lo soluciona.
Puse estas líneas (Swift 3.0) en mi controlador de finalización de carga de elementos
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
self.tableView.isHidden = false
self.loader.stopAnimating()
self.tableView.reloadData()
})
Esto explica por qué para algunas personas, pon un reloadData en layoutSubviews resuelve el problema
llamando a cell.layoutIfNeeded()
dentro de cellForRowAt
me funcionó en ios 10 e ios 11, pero no en ios 9.
para hacer este trabajo en iOS 9 también, llamo a cell.layoutSubviews()
y lo hice.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
// call the method dynamiclabelHeightForText
}
utilice el método anterior que devuelve la altura de la fila de forma dinámica. Y asigne la misma altura dinámica a la etiqueta que esté usando.
-(int)dynamiclabelHeightForText:(NSString *)text :(int)width :(UIFont *)font
{
CGSize maximumLabelSize = CGSizeMake(width,2500);
CGSize expectedLabelSize = [text sizeWithFont:font
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
return expectedLabelSize.height;
}
Este código lo ayuda a encontrar la altura dinámica para el texto que se muestra en la etiqueta.