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.
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;
}
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.
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.
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;
}
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.
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
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.
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
}
}
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.
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 .
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é.
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
// }
}
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.
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)
}
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 :)
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];
}
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
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.
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é.
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.
- (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.
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()
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:)
.