web-dev-qa-db-fra.com

Les hauteurs de cellules dynamiques UITableView ne sont correctes qu'après un certain défilement

J'ai un UITableView avec une personnalisation UITableViewCell définie dans un storyboard utilisant la mise en page automatique. La cellule a plusieurs multilignes UILabels.

Le UITableView semble calculer correctement la hauteur des cellules, mais pour les premières cellules, cette hauteur n'est pas correctement divisée entre les étiquettes. Après un peu de défilement, tout fonctionne comme prévu (même les cellules initialement incorrectes).

- (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;
}

Y a-t-il quelque chose qui me manque, qui empêche la mise en page automatique de faire son travail les premières fois?

J'ai créé un exemple de projet qui illustre ce comportement.

Sample project: Top of table view from sample project on first load.Sample project: Same cells after scrolling down and back up.

98
blackp

Je ne sais pas si cela est clairement documenté ou non, mais en ajoutant [cell layoutIfNeeded] avant de retourner cellule résout votre problème.

- (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;
}
125
rintaro

Cela a fonctionné pour moi alors que d'autres solutions similaires ne fonctionnaient pas:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    layoutIfNeeded()
}

Cela semble être un véritable bogue, car je connais très bien AutoLayout et comment utiliser UITableViewAutomaticDimension. Cependant, je rencontre encore parfois ce problème. Je suis heureux d'avoir enfin trouvé quelque chose qui fonctionne comme une solution de contournement.

34
Michael Peterson

Ajouter [cell layoutIfNeeded] dans cellForRowAtIndexPath ne fonctionne pas pour les cellules initialement défilées.

Le préfacer ne le fait pas non plus avec [cell setNeedsLayout].

Vous devez toujours faire défiler certaines cellules pour les redimensionner correctement.

C'est assez frustrant, car la plupart des développeurs ont le fonctionnement dynamique des cellules de type dynamique, de mise en forme automatique et de dimensionnement automatique - à l'exception de ce cas agaçant. Ce bogue affecte tous mes "plus grands" contrôleurs de vue de table.

20
Scenario

J'ai eu la même expérience dans l'un de mes projets.

Pourquoi ça arrive?

Cellule conçue dans Storyboard avec une certaine largeur pour certains appareils. Par exemple 400px. Par exemple, votre étiquette a la même largeur. Quand il charge depuis le scénarimage, il a une largeur de 400 pixels.

Voici un problème:

tableView:heightForRowAtIndexPath: appelé avant la disposition des cellules, il s'agit de sous-vues.

Donc, il a calculé la hauteur pour l'étiquette et la cellule avec une largeur de 400px. Mais vous utilisez un appareil avec écran, par exemple, 320 pixels. Et cette hauteur calculée automatiquement est incorrecte. Tout simplement parce que layoutSubviews de la cellule ne se produit qu'après tableView:heightForRowAtIndexPath: Même si vous définissez preferredMaxLayoutWidth pour votre étiquette manuellement dans layoutSubviews, cela n’aide en rien.

Ma solution:

1) Sous-classe UITableView et substitution dequeueReusableCellWithIdentifier:forIndexPath:. Définissez une largeur de cellule égale à la largeur du tableau et forcez la présentation de la cellule.

- (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) Sous-classe UITableViewCell. Définissez preferredMaxLayoutWidth manuellement pour vos étiquettes dans layoutSubviews. Vous devez également disposer manuellement de la disposition contentView, car elle ne se met pas automatiquement après le changement du cadre de la cellule (je ne sais pas pourquoi, mais c'est le cas)

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.contentView layoutIfNeeded];
    self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
13
Vitaliy Gozhenko

J'ai un problème similaire, à la première charge, la hauteur de la ligne n'a pas été calculée, mais après un certain défilement ou passer à un autre écran et je reviens à cet écran, les lignes sont calculées. Lors du premier chargement, mes éléments sont chargés depuis Internet et lors du second chargement, ils sont d'abord chargés depuis Core Data et rechargés depuis Internet. J'ai remarqué que la hauteur des rangées est calculée lors du rechargement depuis Internet. J'ai donc remarqué que lorsque tableView.reloadData () est appelée lors de l'animation de la séquence (même problème avec Push et la séquence actuelle), la hauteur de la ligne n'était pas calculée. Donc, j'ai caché la table à l'initialisation de la vue et mis un chargeur d'activité pour empêcher un effet laid à l'utilisateur et j'appelle tableView.reloadData après 300 ms et maintenant le problème est résolu. Je pense que c'est un bogue UIKit mais cette solution de contournement fait l'affaire.

Je mets ces lignes (Swift 3.0) dans mon gestionnaire de complétion de chargement

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
        self.tableView.isHidden = false
        self.loader.stopAnimating()
        self.tableView.reloadData()
    })

Ceci explique pourquoi, pour certaines personnes, mettre un reloadData dans layoutSubviews résout le problème.

9
alvinmeimoun

Dans mon cas, la dernière ligne de UILabel a été tronquée lorsque la cellule a été affichée pour la première fois. Cela s'est passé assez au hasard et la seule façon de le dimensionner correctement était de faire défiler la cellule hors de la vue et de la ramener. J'ai essayé toutes les solutions possibles affichées jusqu'à présent (layoutIfNeeded..reloadData) mais rien n'a fonctionné pour moi. L'astuce consistait à régler "Autoshrink" sur Échelle de police minimale (0,5 pour moi). Essaie

5
Claus

J'ai essayé la plupart des réponses à cette question et je ne pouvais en faire fonctionner aucune. La seule solution fonctionnelle que j'ai trouvée a été d'ajouter ce qui suit à ma sous-classe UITableViewController:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    UIView.performWithoutAnimation {
        tableView.beginUpdates()
        tableView.endUpdates()
    }
}

Le UIView.performWithoutAnimation appel est requis, sinon vous verrez l'animation normale de la vue de la table pendant le chargement du contrôleur de vue.

4
Chris Vig

Ajoutez une contrainte pour tout le contenu d'une cellule personnalisée de vue tableau, puis estimez la hauteur des lignes de la vue tableau et définissez la hauteur des lignes sur une dimension automatique avec dans un chargement viewdid:

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

Pour résoudre ce problème de chargement initial, appliquez la méthode layoutIfNeeded dans une cellule de vue de tableau personnalisée:

class CustomTableViewCell: UITableViewCell {

override func awakeFromNib() {
    super.awakeFromNib()
    self.layoutIfNeeded()
    // Initialization code
}
}
4
bikram sapkota

J'ai essayé toutes les solutions de cette page, mais en décochant l'utilisation des classes de taille, puis en le vérifiant, j'ai résolu mon problème.

Éditer: Décocher les classes de taille pose beaucoup de problèmes sur le storyboard, alors j’ai essayé une autre solution. J'ai renseigné ma vue de tableau dans les méthodes viewDidLoad et viewWillAppear de mon contrôleur de vue. Cela a résolu mon problème.

2
ACengiz

Attatching screenshot for your referance Pour moi, aucune de ces approches n'a fonctionné, mais j'ai découvert que l'étiquette avait un Preferred Width défini dans Interface Builder. Supprimer cela (décocher "Explicit") puis utiliser UITableViewAutomaticDimension a fonctionné comme prévu.

2
Jack James

Définir preferredMaxLayoutWidth aide dans mon cas. J'ai ajouté

cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width

dans mon code.

Reportez-vous également à Le texte sur une ligne prend deux lignes dans UILabel et http://openradar.appspot.com/17799811 .

2
Xhacker Liu

l'appel de cell.layoutIfNeeded() à l'intérieur de cellForRowAt a fonctionné pour moi sur iOS 10 et iOS 11, mais pas sur iOS 9.

pour obtenir ce travail également sur iOS 9, j’appelle cell.layoutSubviews() et c’est ce qui a fonctionné.

2
mnemonic23

Dans mon cas, je mettais à jour dans un autre cycle. Ainsi, hauteur de tableViewCell a été mise à jour après la définition de labelText. J'ai supprimé le bloc async.

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
     // }
}
1
Den Jo

aucune des solutions ci-dessus n'a fonctionné pour moi. Ce qui a bien fonctionné, c'est cette recette de magie: appelez-les dans cet ordre:

tableView.reloadData()
tableView.layoutIfNeeded() tableView.beginUpdates() tableView.endUpdates()

mes données de tableView sont remplies à partir d'un service Web, dans le rappel de la connexion, j'écris les lignes ci-dessus.

1
JAHelia

J'ai le problème avec l'étiquette de redimensionnement, donc je ne fais que faire
chatTextLabel.text = chatMessage.message chatTextLabel? .updateConstraints () après la configuration du texte

// code complet

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)

  }
1
Svitlana

Assurez-vous simplement que vous ne définissez pas le texte de l'étiquette dans la méthode de délégation 'willdisplaycell' de la vue tableau. Définissez le texte de l'étiquette dans la méthode déléguée 'cellForRowAtindexPath' pour le calcul dynamique de la hauteur.

Vous êtes les bienvenus :)

1
Pranav Rivankar

Le problème est que les cellules initiales se chargent avant que nous ayons une hauteur de ligne valide. La solution de contournement consiste à forcer un rechargement de table lorsque la vue apparaît.

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}
1
alicanozkara

Dans Swift 3. J'ai dû appeler self.layoutIfNeeded () chaque fois que je mets à jour le texte de la cellule réutilisable.

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
0
Naloiko Eugene

Pour tous ceux qui sont encore confrontés à ce problème, essayez cette ligne magique

  tableview.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)

Assurez-vous simplement que vous appelez cette table après le remplissage avec des données afin de vous assurer que la section 0 et la ligne 0 sont toujours disponibles, sinon elle se bloque, vous pouvez vérifier leur disponibilité en premier lieu, en espérant que cela vous aide.

0
IsPha

Dans mon cas, le problème avec la hauteur de la cellule survient après le chargement de la vue de table initiale et une action de l'utilisateur a lieu (appui sur un bouton d'une cellule ayant pour effet de modifier la hauteur de la cellule). J'ai été incapable de faire changer la hauteur de la cellule si ce n'est:

[self.tableView reloadData];

J'ai essayé

[cell layoutIfNeeded];

mais ça n'a pas marché.

0
Chris Prince

Dans mon cas, une vue de pile dans la cellule était à l'origine du problème. C'est un bug apparemment. Une fois que je l'ai enlevé, le problème a été résolu.

0
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{


//  call the method dynamiclabelHeightForText
}

utilisez la méthode ci-dessus qui renvoie dynamiquement la hauteur de la ligne. Et assignez la même hauteur dynamique au lable que vous utilisez.

-(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;


}

Ce code vous aide à trouver la hauteur dynamique pour l'affichage du texte dans l'étiquette.

0
Ramesh Muthe

Aucune des solutions ci-dessus n'a fonctionné, mais la combinaison suivante de suggestions a fonctionné.

A dû ajouter ce qui suit dans viewDidLoad ().

DispatchQueue.main.async {

        self.tableView.reloadData()

        self.tableView.setNeedsLayout()
        self.tableView.layoutIfNeeded()

        self.tableView.reloadData()

    }

La combinaison ci-dessus de reloadData, setNeedsLayout et layoutIfNeeded a fonctionné, mais aucune autre. Pourrait être spécifique aux cellules du projet cependant. Et oui, il a fallu invoquer reloadData deux fois pour que cela fonctionne.

Définissez également les éléments suivants dans viewDidLoad

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight

Dans tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath)

cell.setNeedsLayout()
cell.layoutIfNeeded() 
0

J'ai rencontré ce problème et je l'ai corrigé en déplaçant mon code d'initialisation de vue/étiquette DE tableView(willDisplay cell:) à tableView(cellForRowAt:).

0
raisedandglazed