working uitableviewcell not ios uitableview swift autolayout

ios - uitableviewcell - reloadData() de UITableView con alturas de celda dinámicas provoca un desplazamiento irregular



uitableviewautomaticdimension (17)

Aquí hay una versión un poco más corta:

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return self.cellHeightsDictionary[indexPath] ?? UITableViewAutomaticDimension }

Siento que esto podría ser un problema común y me preguntaba si había alguna solución común.

Básicamente, mi UITableView tiene alturas de celda dinámicas para cada celda. Si no estoy en la parte superior de UITableView y yo tableView.reloadData() , el desplazamiento hacia arriba se vuelve nervioso.

Creo que esto se debe al hecho de que debido a que volví a cargar datos, a medida que avanzo, el UITableView está recalculando la altura de cada celda que entra en visibilidad. ¿Cómo mitigo eso, o cómo solo recargo datos desde un IndexPath determinado hasta el final de UITableView?

Además, cuando me las arreglo para desplazarme hasta la parte superior, puedo desplazarme hacia abajo y luego hacia arriba, sin problemas sin saltar. Esto es muy probable porque las alturas UITableViewCell ya se calcularon.


De hecho, puede recargar solo ciertas filas utilizando reloadRowsAtIndexPaths , por ejemplo:

func reloadSectionWithouAnimation(section: Int) { UIView.performWithoutAnimation { let offset = self.contentOffset self.reloadSections(IndexSet(integer: section), with: .none) self.contentOffset = offset } }

Pero, en general, también podría animar los cambios de altura de celda de la tabla de esta manera:

tableView.reloadSectionWithouAnimation(section: indexPath.section)


El salto se debe a una mala altura estimada. Cuanto más se diferencie el valor estimado de RowHeight de la altura real, más puede saltar la mesa cuando se vuelve a cargar, especialmente cuanto más abajo se haya desplazado. Esto se debe a que el tamaño estimado de la tabla difiere radicalmente de su tamaño real, lo que obliga a la tabla a ajustar su tamaño de contenido y su compensación. Por lo tanto, la altura estimada no debe ser un valor aleatorio sino cercano a lo que crees que va a ser la altura. También he experimentado cuando configuro UITableViewAutomaticDimension si sus celdas son del mismo tipo que

func viewDidLoad() { super.viewDidLoad() tableView.estimatedRowHeight = 100//close to your cell height }

si tienes variedad de celdas en diferentes secciones, entonces creo que el mejor lugar es

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { //return different sizes for different cells if you need to return 100 }


Este me funcionó en Swift4:

extension UITableView { func reloadWithoutAnimation() { let lastScrollOffset = contentOffset reloadData() layoutIfNeeded() setContentOffset(lastScrollOffset, animated: false) } }


Hay un error que creo que se introdujo en iOS11.

Es entonces cuando vuelve a reload el tableView contentOffSet se altera inesperadamente. De hecho, contentOffset no debería cambiar después de una recarga. Tiende a suceder debido a errores de cálculo de UITableViewAutomaticDimension

contentOffSet guardar su contentOffSet y volver a establecerlo en su valor guardado una vez que finalice la recarga.

func reloadTableOnMain(with offset: CGPoint = CGPoint.zero){ DispatchQueue.main.async { [weak self] () in self?.tableView.reloadData() self?.tableView.layoutIfNeeded() self?.tableView.contentOffset = offset } }

¿Cómo lo usas?

someFunctionThatMakesChangesToYourDatasource() let offset = tableview.contentOffset reloadTableOnMain(with: offset)

Esta respuesta se deriva de here


He intentado todas las soluciones anteriores, pero nada funcionó.

Después de pasar horas y pasar por todas las frustraciones posibles, descubrí una manera de solucionar esto. ¡Esta solución es un salvavidas! ¡Trabajado como un encanto!

Swift 4

let lastContentOffset = tableView.contentOffset tableView.beginUpdates() tableView.endUpdates() tableView.layer.removeAllAnimations() tableView.setContentOffset(lastContentOffset, animated: false)

Lo agregué como una extensión, para hacer que el código se vea más limpio y evitar escribir todas estas líneas cada vez que quiera volver a cargar.

extension UITableView { func reloadWithoutAnimation() { let lastScrollOffset = contentOffset beginUpdates() endUpdates() layer.removeAllAnimations() setContentOffset(lastScrollOffset, animated: false) } }

finalmente ..

tableView.reloadWithoutAnimation()

O bien, podría agregar estas líneas en su UITableViewCell awakeFromNib()

layer.shouldRasterize = true layer.rasterizationScale = UIScreen.main.scale

y hacer reloadData() normal reloadData()


Intente llamar a cell.layoutSubviews() antes de devolver la celda en func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell? . Es un error conocido en iOS8.


Me encontré con esto hoy y observé:

  1. Es solo iOS 8, de hecho.
  2. Anular cellForRowAtIndexPath no ayuda.

La solución fue en realidad bastante simple:

Reemplace estimatedHeightForRowAtIndexPath y asegúrese de que devuelva los valores correctos.

Con esto, todos los temblores y saltos extraños en mis UITableViews se han detenido.

NOTA: De hecho, sé el tamaño de mis celdas. Solo hay dos valores posibles. Si sus celdas son verdaderamente de tamaño variable, entonces es posible que desee almacenar en caché cell.bounds.size.height de tableView:willDisplayCell:forRowAtIndexPath:


Ninguna de estas soluciones funcionó para mí. Esto es lo que hice con Swift 4 y Xcode 10.1 ...

En viewDidLoad (), declare la altura de fila dinámica de la tabla y cree las restricciones correctas en las celdas ...

tableView.rowHeight = UITableView.automaticDimension

También en viewDidLoad (), registre todas sus puntas de celda tableView en tableview de esta manera:

tableView.register(UINib(nibName: "YourTableViewCell", bundle: nil), forCellReuseIdentifier: "YourTableViewCell") tableView.register(UINib(nibName: "YourSecondTableViewCell", bundle: nil), forCellReuseIdentifier: "YourSecondTableViewCell") tableView.register(UINib(nibName: "YourThirdTableViewCell", bundle: nil), forCellReuseIdentifier: "YourThirdTableViewCell")

En tableView heightForRowAt, devuelve una altura igual a la altura de cada celda en indexPath.row ...

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.row == 0 { let cell = Bundle.main.loadNibNamed("YourTableViewCell", owner: self, options: nil)?.first as! YourTableViewCell return cell.layer.frame.height } else if indexPath.row == 1 { let cell = Bundle.main.loadNibNamed("YourSecondTableViewCell", owner: self, options: nil)?.first as! YourSecondTableViewCell return cell.layer.frame.height } else { let cell = Bundle.main.loadNibNamed("YourThirdTableViewCell", owner: self, options: nil)?.first as! YourThirdTableViewCell return cell.layer.frame.height } }

Ahora proporcione una altura de fila estimada para cada celda en tableView EstimatedHeightForRowAt. Sé lo más preciso que puedas ...

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.row == 0 { return 400 // or whatever YourTableViewCell''s height is } else if indexPath.row == 1 { return 231 // or whatever YourSecondTableViewCell''s height is } else { return 216 // or whatever YourThirdTableViewCell''s height is } }

Eso debería funcionar...

No necesitaba guardar y establecer contentOffset al llamar a tableView.reloadData ()


Para evitar saltos, debe guardar las alturas de las celdas cuando se cargan y dar el valor exacto en tableView:estimatedHeightForRowAtIndexPath :

// declare cellHeightsDictionary NSMutableDictionary *cellHeightsDictionary; // initialize in code (thanks to @Gerharbo) cellHeightsDictionary = @{}.mutableCopy; // declare table dynamic row height and create correct constraints in cells tableView.rowHeight = UITableViewAutomaticDimension; // save height - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { [cellHeightsDictionary setObject:@(cell.frame.size.height) forKey:indexPath]; } // give exact height value - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { NSNumber *height = [cellHeightsDictionary objectForKey:indexPath]; if (height) return height.doubleValue; return UITableViewAutomaticDimension; }


Puede usar lo siguiente en ViewDidLoad()

tableView.estimatedRowHeight = 0 // if have just tableViewCells <br/> // use this if you have tableview Header/footer <br/> tableView.estimatedSectionFooterHeight = 0 <br/> tableView.estimatedSectionHeaderHeight = 0


Reemplazar el método estimadoHeightForRowAtIndexPath con un valor alto, por ejemplo 300f

Esto debería solucionar el problema :)


Tengo 2 alturas de celda diferentes.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160) return Helper.makeDeviceSpecificCommonSize(cellHeight) }

Después de agregar estimadoHeightForRowAt , no hubo más saltos.

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160) return Helper.makeDeviceSpecificCommonSize(cellHeight) }


Tuve este comportamiento de salto e inicialmente pude mitigarlo estableciendo la altura exacta estimada del encabezado (porque solo tenía 1 vista de encabezado posible), sin embargo, los saltos comenzaron a ocurrir dentro de los encabezados específicamente, sin afectar a toda la tabla.

Siguiendo las respuestas aquí, tuve la idea de que estaba relacionado con las animaciones, por lo que descubrí que la vista de tabla estaba dentro de una vista de pila, y a veces llamamos stackView.layoutIfNeeded() dentro de un bloque de animación. Mi solución final fue asegurarme de que esta llamada no suceda a menos que sea "realmente" necesaria, porque el diseño "si es necesario" tenía comportamientos visuales en ese contexto, incluso cuando "no era necesario".


Utilizo más formas de solucionarlo:

Para el controlador de vista:

tableView.reloadRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None)

como la extensión para UITableView

tableView.beginUpdates() tableView.endUpdates()

El resultado es

var cellHeights: [IndexPath : CGFloat] = [:] func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cellHeights[indexPath] = cell.frame.size.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return cellHeights[indexPath] ?? 70.0 }


Versión Swift 3 de la respuesta aceptada.

var cellHeights: [IndexPath : CGFloat] = [:] func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cellHeights[indexPath] = cell.frame.size.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return cellHeights[indexPath] ?? 70.0 }


La respuesta de @Igor funciona bien en este caso, el código Swift-4 de la misma.

// declaration & initialization var cellHeightsDictionary: [IndexPath: CGFloat] = [:]

en los siguientes métodos de UITableViewDelegate

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { // print("Cell height: /(cell.frame.size.height)") self.cellHeightsDictionary[indexPath] = cell.frame.size.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { if let height = self.cellHeightsDictionary[indexPath] { return height } return UITableViewAutomaticDimension }