Je pense que cela pourrait être un problème commun et je me demandais s'il existait une solution commune à ce problème.
Fondamentalement, mon UITableView a des hauteurs de cellule dynamiques pour chaque cellule. Si je ne suis pas au sommet de UITableView et que je tableView.reloadData()
, le défilement devient aléatoire.
Je pense que cela est dû au fait que, parce que j'ai rechargé des données et que je fais défiler l'écran vers le haut, UITableView recalcule la hauteur de chaque cellule entrant en visibilité. Comment puis-je atténuer cela, ou comment ne recharger des données qu'à partir d'un certain IndexPath jusqu'à la fin de UITableView?
De plus, quand je parviens à faire défiler tout le chemin jusqu'au sommet, je peux faire défiler vers le bas, puis vers le haut, sans problème, sans sauter. Cela est probablement dû au fait que les hauteurs UITableViewCell ont déjà été calculées.
Pour éviter les sauts, vous devez enregistrer les hauteurs de cellules lors de leur chargement et donner la valeur exacte dans 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;
}
Swift 3 version de réponse acceptée.
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
}
Le saut est dû à une mauvaise hauteur estimée. Plus le hauteur estimée de la propriété RowReight diffère de la hauteur réelle, plus la table risque de sauter lorsqu'elle est rechargée, en particulier si le défilement est terminé. En effet, la taille estimée de la table diffère radicalement de sa taille réelle, ce qui oblige la table à ajuster la taille et le décalage de son contenu . être. J'ai aussi vécu quand je mets UITableViewAutomaticDimension
Si vos cellules sont du même type, puis
func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100//close to your cell height
}
si vous avez une variété de cellules dans différentes sections, alors je pense que le meilleur endroit est
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//return different sizes for different cells if you need to
return 100
}
@Igoranswer fonctionne correctement dans ce cas, le code Swift-4
.
// declaration & initialization
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]
dans les méthodes suivantes 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
}
Je suis tombé dessus aujourd'hui et j'ai observé:
cellForRowAtIndexPath
n'aide pas.Le correctif était en fait assez simple:
Remplacez estimatedHeightForRowAtIndexPath
et assurez-vous qu'il renvoie les valeurs correctes.
Avec cela, tous les trépidations et sautillements étranges dans mes UITableViews se sont arrêtés.
REMARQUE: je connais réellement la taille de mes cellules. Il n'y a que deux valeurs possibles. Si vos cellules ont vraiment une taille variable, vous pouvez alors mettre en cache le cell.bounds.size.height
à partir de tableView:willDisplayCell:forRowAtIndexPath:
Vous ne pouvez en fait recharger que certaines lignes en utilisant reloadRowsAtIndexPaths
, ex:
tableView.reloadRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None)
Mais, en général, vous pouvez également animer les changements de hauteur des cellules du tableau comme suit:
tableView.beginUpdates()
tableView.endUpdates()
J'ai essayé toutes les solutions de contournement ci-dessus, mais rien n'a fonctionné.
Après avoir passé des heures et passé en revue toutes les frustrations possibles, nous avons trouvé un moyen de résoudre ce problème. Cette solution est un sauveur de vie! Travaillé comme un charme!
Swift 4
let lastContentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastContentOffset, animated: false)
Je l'ai ajouté en tant qu'extension afin de donner au code une apparence plus nette et d'éviter d'écrire toutes ces lignes chaque fois que je souhaite recharger.
extension UITableView {
func reloadWithoutAnimation() {
let lastScrollOffset = contentOffset
beginUpdates()
endUpdates()
layer.removeAllAnimations()
setContentOffset(lastScrollOffset, animated: false)
}
}
enfin ..
tableView.reloadWithoutAnimation()
J'utilise plusieurs façons de résoudre ce problème:
Pour le contrôleur de vue:
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
}
comme extension pour UITableView
func reloadSectionWithouAnimation(section: Int) {
UIView.performWithoutAnimation {
let offset = self.contentOffset
self.reloadSections(IndexSet(integer: section), with: .none)
self.contentOffset = offset
}
}
Le résultat est
tableView.reloadSectionWithouAnimation(section: indexPath.section)
Voici une version un peu plus courte:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.cellHeightsDictionary[indexPath] ?? UITableViewAutomaticDimension
}
Celui-ci a fonctionné pour moi dans Swift4:
extension UITableView {
func reloadWithoutAnimation() {
let lastScrollOffset = contentOffset
reloadData()
layoutIfNeeded()
setContentOffset(lastScrollOffset, animated: false)
}
}
J'ai 2 hauteurs de cellules différentes.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
return Helper.makeDeviceSpecificCommonSize(cellHeight)
}
Après avoir ajouté installedHeightForRowAt , il n'y avait plus de sauts.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
return Helper.makeDeviceSpecificCommonSize(cellHeight)
}
Vous pouvez utiliser ce qui suit dans 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
Aucune de ces solutions n'a fonctionné pour moi. Voici ce que j'ai fait avec Swift 4 & Xcode 10.1 ...
Dans viewDidLoad (), déclarez la hauteur de ligne dynamique de la table et créez des contraintes correctes dans les cellules ...
tableView.rowHeight = UITableView.automaticDimension
Également dans viewDidLoad (), enregistrez tous vos numéros de cellules tableView dans tableview comme ceci:
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")
Dans tableView heightForRowAt, renvoyez la hauteur égale à la hauteur de chaque cellule dans 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
}
}
Maintenant, donnez une hauteur de ligne estimée pour chaque cellule de tableView installedHeightForRowAt. Soyez précis que vous pouvez ...
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
}
}
Cela devrait fonctionner...
Je n'avais pas besoin de sauvegarder et de définir contentOffset lors de l'appel de tableView.reloadData ()
Redéfinition de la méthode ratedHeightForRowAtIndexPath par une valeur élevée, par exemple 300f
Cela devrait résoudre le problème :)
Il y a un bug qui, je crois, a été introduit dans iOS11.
C’est à ce moment-là que vous faites une reload
et que tableView contentOffSet
est modifié de façon inattendue. En fait, contentOffset
ne devrait pas changer après un rechargement. Cela a tendance à se produire en raison d'erreurs de calcul de UITableViewAutomaticDimension
Vous devez enregistrer votre contentOffSet
et la rétablir sur votre valeur enregistrée une fois le rechargement terminé.
func reloadTableOnMain(with offset: CGPoint = CGPoint.zero){
DispatchQueue.main.async { [weak self] () in
self?.tableView.reloadData()
self?.tableView.layoutIfNeeded()
self?.tableView.contentOffset = offset
}
}
Comment vous l'utilisez?
someFunctionThatMakesChangesToYourDatasource()
let offset = tableview.contentOffset
reloadTableOnMain(with: offset)
Cette réponse est dérivée de ici
Essayez d'appeler cell.layoutSubviews()
avant de retourner la cellule dans func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?
. C'est un bug connu dans iOS8.