ios - Desplazamiento desigual después de actualizar UITableViewCell en su lugar con UITableViewAutomaticDimension
swift ios8 (6)
Aquí está la mejor solución que encontré para resolver este tipo de problema (problema de desplazamiento + reloadRows + iOS 8 UITableViewAutomaticDimension);
Consiste en mantener todas las alturas en un diccionario y actualizarlas (en el diccionario) ya que tableView mostrará la celda.
Luego devolverá la altura guardada en
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
Método
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
.
Deberías implementar algo como esto:
C objetivo
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
if(height) {
return height.floatValue;
} else {
return UITableViewAutomaticDimension;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = @(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
Swift 3
@IBOutlet var tableView : UITableView?
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
Estoy creando una aplicación que tiene una vista de feed para las publicaciones enviadas por los usuarios.
Esta vista tiene un
UITableView
con una implementación personalizada de
UITableViewCell
.
Dentro de esta celda, tengo otro
UITableView
para mostrar comentarios.
La esencia es algo como esto:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
El feed inicial se descargará con 3 comentarios para obtener una vista previa, pero si hay más comentarios, o si el usuario agrega o elimina un comentario, quiero actualizar
PostCell
en su lugar dentro de la vista de la tabla de feed agregando o eliminando
CommentCells
a los comentarios mesa dentro del
PostCell
.
Actualmente estoy usando el siguiente ayudante para lograr eso:
// (PostCell.swift) Handle showing/hiding comments
func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) {
let table = self.superview?.superview as UITableView
// "table" is outer feed table
// self is the PostCell that is updating it''s comments
// self.comments is UITableView for displaying comments inside of the PostCell
table.beginUpdates()
self.comments.beginUpdates()
// This function handles inserting/removing/reloading a range of comments
// so we build out an array of index paths for each row that needs updating
var indexPaths = [NSIndexPath]()
for var index = startRow; index <= endRow; index++ {
indexPaths.append(NSIndexPath(forRow: index, inSection: 0))
}
switch operation {
case .INSERT:
self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .DELETE:
self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .RELOAD:
self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
self.comments.endUpdates()
table.endUpdates()
// trigger a call to updateConstraints so that we can update the height constraint
// of the comments table to fit all of the comments
self.setNeedsUpdateConstraints()
}
override func updateConstraints() {
super.updateConstraints()
self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height
}
Esto logra la actualización muy bien.
La publicación se actualiza en su lugar con comentarios agregados o eliminados dentro de
PostCell
como se esperaba.
Estoy usando
PostCells
tamaño
PostCells
en la tabla de alimentación.
La tabla de comentarios de
PostCell
expande para mostrar todos los comentarios, pero la animación es un poco irregular y la tabla se desplaza hacia arriba y hacia abajo una docena de píxeles más o menos mientras se realiza la animación de actualización de celda.
El salto durante el cambio de tamaño es un poco molesto, pero mi problema principal viene después.
Ahora, si me desplazo hacia abajo en el feed, el desplazamiento es suave como antes, pero si me desplazo hacia arriba por encima de la celda que acabo de cambiar de tamaño después de agregar comentarios, el feed saltará hacia atrás varias veces antes de llegar a la parte superior del feed.
Configuré las celdas de tamaño automático de
iOS8
para el Feed de esta manera:
// (FeedController.swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
Si elimino el
estimatedRowHeight
, la tabla se desplaza hacia la parte superior cada vez que cambia la altura de una celda.
Me siento bastante atrapado en esto ahora y, como nuevo desarrollador de iOS, podría usar cualquier consejo que pueda tener.
Estaba enfrentando el mismo problema también. Encontré una solución, pero no soluciona por completo el tirón. Pero parece ser mucho mejor en comparación con el desplazamiento entrecortado anterior.
En su método delegado
UITableView
:cellForRowAtIndexPath:
intente usar los dos métodos siguientes para actualizar las restricciones antes de devolver la celda.
(Lenguaje rápido)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
EDITAR:
También puede que tenga que jugar con el valor
tableView.estimatedRowHeight
para obtener un desplazamiento más suave.
La solución @dosdos funciona bien
pero hay algo que deberías agregar
siguiendo la respuesta de @dosdos
Swift 3/4
@IBOutlet var tableView : UITableView!
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
luego use estas líneas cuando quiera, para mí las uso dentro de textDidChange
- primera recarga Tableview
- restricción de actualización
-
finalmente pasar a la parte superior de Tableview
tableView.reloadData() self.tableView.layoutIfNeeded() self.tableView.setContentOffset(CGPoint.zero, animated: true)
Siguiendo la respuesta de @dosdos .
También me pareció interesante implementar:
tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Especialmente para mi código, donde la celda está cambiando las restricciones dinámicamente mientras la celda ya se muestra en la pantalla. Actualizar el diccionario de esta manera ayuda la segunda vez que se muestra la celda.
var heightAtIndexPath = [NSIndexPath : NSNumber]()
....
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = UITableViewAutomaticDimension
....
extension TableViewViewController: UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = heightAtIndexPath[indexPath]
if let height = height {
return CGFloat(height)
}
else {
return UITableViewAutomaticDimension
}
}
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
}
Tuvimos el mismo problema.
Proviene de una mala estimación de la altura de la celda que hace que el SDK fuerce una mala altura, lo que provocará el salto de las células al desplazarse hacia arriba.
Dependiendo de cómo construyó su celda, la mejor manera de solucionar esto es implementar el método
UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
Mientras su estimación sea bastante cercana al valor real de la altura de la celda, esto casi cancelará los saltos y las sacudidas. Así es como lo implementamos, obtendrá la lógica:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// This method will get your cell identifier based on your data
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kFirstCellIdentifier])
return kFirstCellHeight;
else if ([cellType isEqualToString:kSecondCellIdentifier])
return kSecondCellHeight;
else if ([cellType isEqualToString:kThirdCellIdentifier])
return kThirdCellHeight;
else {
return UITableViewAutomaticDimension;
}
}
Soporte Swift 2 agregado
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// This method will get your cell identifier based on your data
let cellType = reuseIdentifierForIndexPath(indexPath)
if cellType == kFirstCellIdentifier
return kFirstCellHeight
else if cellType == kSecondCellIdentifier
return kSecondCellHeight
else if cellType == kThirdCellIdentifier
return kThirdCellHeight
else
return UITableViewAutomaticDimension
}
la respuesta de dosdos funcionó para mí en Swift 2
Declara el ivar
var heightAtIndexPath = NSMutableDictionary()
en func viewDidLoad ()
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Luego agregue los siguientes 2 métodos:
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = self.heightAtIndexPath.objectForKey(indexPath)
if ((height) != nil) {
return CGFloat(height!.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let height = cell.frame.size.height
self.heightAtIndexPath.setObject(height, forKey: indexPath)
}
SWIFT 3:
var heightAtIndexPath = [IndexPath: CGFloat]()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightAtIndexPath[indexPath] = cell.frame.size.height
}