UILabel sizeToFit ne fonctionne pas avec la mise en page automatique ios6

152

Comment suis-je censé configurer par programme (et dans quelle méthode) un UILabel dont la hauteur dépend de son texte? J'ai essayé de le configurer en utilisant une combinaison de Storyboard et de code, mais en vain. Tout le monde recommande sizeToFitlors du réglage lineBreakModeet numberOfLines. Cependant, peu importe si je mets ce code dans viewDidLoad:, viewDidAppear:ou viewDidLayoutSubviewsje ne peux pas le faire au travail. Soit je fais la boîte trop petite pour du texte long et elle ne grossit pas, soit je la fais trop grande et elle ne rétrécit pas.

circuitlego
la source
FWIW le même code: je n'avais pas besoin d'utiliser label.sizeToFit()dans Xcode / viewController, les contraintes étaient suffisantes. ne créait pas l'étiquette dans Playground . Jusqu'à présent, la seule façon dont je l'ai trouvé pour fonctionner dans Playground est de le fairelabel.sizeToFit()
Honey

Réponses:

407

Veuillez noter que dans la plupart des cas, la solution de Matt fonctionne comme prévu. Mais si cela ne fonctionne pas pour vous, veuillez lire la suite.

Pour que votre étiquette redimensionne automatiquement la hauteur, vous devez procéder comme suit:

  1. Définir des contraintes de mise en page pour l'étiquette
  2. Définissez la contrainte de hauteur avec une priorité faible. Il doit être inférieur à ContentCompressionResistancePriority
  3. Définir numberOfLines = 0
  4. Définir ContentHuggingPriority plus haut que la priorité de hauteur de l'étiquette
  5. Définissez preferMaxLayoutWidth pour l'étiquette. Cette valeur est utilisée par l'étiquette pour calculer sa hauteur

Par exemple:

self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;

[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];

NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[descriptionLabel_(220@300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];

Utilisation d'Interface Builder

  1. Définissez quatre contraintes. La contrainte de hauteur est obligatoire. entrez la description de l'image ici

  2. Accédez ensuite à l'inspecteur des attributs de l'étiquette et définissez le nombre de lignes sur 0. entrez la description de l'image ici

  3. Accédez à l'inspecteur de taille de l'étiquette et augmentez ContentHuggingPriority vertical et ContentCompressionResistancePriority vertical.
    entrez la description de l'image ici

  4. Sélectionnez et modifiez la contrainte de hauteur.
    entrez la description de l'image ici

  5. Et diminuez la priorité des contraintes de hauteur.
    entrez la description de l'image ici

Prendre plaisir. :)

Mark Kryzhanouski
la source
4
OUI! C'est exactement la réponse que je cherchais. La seule chose que j'avais à faire était de définir contentHuggingPriority et contentCompressionResistancePriority sur required. Je pourrais tout faire à IB! Merci beaucoup.
circuitlego
43
Vous y réfléchissez trop. Définissez preferredMaxLayoutWidthou épinglez la largeur de l'étiquette (ou ses côtés droit et gauche). C'est tout! Il se développera alors automatiquement et se rétrécira verticalement pour s'adapter à son contenu.
mat
J'ai dû définir la propriété favoriteMaxLayoutWidth + épingler la largeur de l'étiquette. Fonctionne comme un charme :)
MartinMoizard
preferMaxLayoutWidth et / ou épingler les côtés gauche et droit n'est pas absolu. Les priorités de compression et de compression du contenu fonctionneront toujours tant que leurs priorités sont plus élevées que le reste des contraintes.
Eric Alford
2
^ Et j'ai enfin découvert pourquoi, cela vaut peut-être la peine de mentionner, lorsque vous avez la cellule collée au bas de la vue, vous devez définir la priorité de la contrainte "vers le bas" à moins de 1000, je la mets sur 750 et tout fonctionne parfaitement. Je suppose que cela a réduit la vue.
Mathijs Segers
65

Dans iOS 6, en utilisant la mise en page automatique, si les côtés (ou la largeur) et le dessus d'un UILabel sont épinglés, il se développera automatiquement et se rétrécira verticalement pour s'adapter à son contenu, sans code du tout et sans gâcher sa résistance à la compression ou autre. C'est très simple.

Dans les cas plus complexes, définissez simplement les étiquettes preferredMaxLayoutWidth.

Dans tous les cas, la bonne chose se produit automatiquement.

mat
la source
12
Vous devez également vous assurer de définir numberOfLines sur 0, sinon vous n'obtiendrez pas de wrapping.
devongovett
2
Cela ne me suffisait pas. Il fallait également le point 2 de la réponse de @ Mark.
Danyal Aytekin
est-il également possible de faire croître automatiquement l'étiquette sur plusieurs lignes sans écrire de codes?
Noor
C'est beaucoup moins compliqué que la réponse acceptée. J'ai fait deux exemples ( ici et ici ) illustrant cette réponse.
Suragch
@Suragch En utilisant des contraintes, cela fonctionne. Mais cela ne fonctionne pas pour le même code dans Playground . Dans l'aire de jeux, vous devez avoirlabel.sizeToFit()
Honey
42

Bien que la question se déclare par programme, ayant rencontré le même problème et préférant travailler dans Interface Builder, j'ai pensé qu'il pourrait être utile d'ajouter aux réponses existantes avec une solution Interface Builder.

La première chose est d'oublier sizeToFit. La disposition automatique gérera cela en votre nom en fonction de la taille du contenu intrinsèque.

Le problème est donc de savoir comment obtenir une étiquette pour l'adapter à son contenu avec la mise en page automatique? Plus précisément - parce que la question le mentionne - la hauteur. Notez que les mêmes principes s'appliquent à la largeur.

Commençons donc par un exemple d'UILabel dont la hauteur est fixée à 41 px de haut:

entrez la description de l'image ici

Comme vous pouvez le voir dans la capture d'écran ci-dessus, "This is my text"a un rembourrage au-dessus et en dessous. C'est un remplissage entre la hauteur de l'UILabel et son contenu, le texte.

Si nous exécutons l'application dans le simulateur, bien sûr, nous voyons la même chose:

entrez la description de l'image ici

Maintenant, sélectionnons UILabel dans Interface Builder et examinons les paramètres par défaut dans l'inspecteur de taille:

entrez la description de l'image ici

Notez la contrainte mise en évidence ci-dessus. Telle est la priorité du contenu . Comme le décrit Erica Sadun dans l'excellent iOS Auto Layout Demystified , c'est:

la façon dont une vue préfère éviter un remplissage supplémentaire autour de son contenu principal

Pour nous, avec l'UILabel, le contenu principal est le texte.

Nous arrivons ici au cœur de ce scénario de base. Nous avons donné à notre étiquette de texte deux contraintes. Ils sont en conflit. On dit "la hauteur doit être égale à 41 pixels de haut" . L'autre dit "embrassez la vue sur son contenu afin que nous n'ayons pas de remplissage supplémentaire" . Dans notre cas, étreignez la vue sur son texte pour ne pas avoir de remplissage supplémentaire.

Maintenant, avec la mise en page automatique, avec deux instructions différentes qui disent faire des choses différentes, le moteur d'exécution doit choisir l'une ou l'autre. Il ne peut pas faire les deux. Le UILabel ne peut pas avoir à la fois 41 pixels de haut et n'a pas de remplissage.

La façon dont cela est résolu est de spécifier la priorité. Une instruction doit avoir une priorité plus élevée que l'autre. Si les deux instructions disent des choses différentes et ont la même priorité, une exception se produira.

Alors, essayons. Ma contrainte de hauteur a une priorité de 1000 , ce qui est obligatoire . La hauteur de contention du contenu est de 250 , ce qui est faible . Que se passe-t-il si nous réduisons la priorité de la contrainte de hauteur à 249 ?

entrez la description de l'image ici

Maintenant, nous pouvons voir la magie commencer à se produire. Essayons dans la sim:

entrez la description de l'image ici

Impressionnant! Étreinte du contenu réalisée. Uniquement parce que la priorité de hauteur 249 est inférieure à la priorité de contenu 250 . Fondamentalement, je dis "la hauteur que je spécifie ici est moins importante que ce que j'ai spécifié pour l'étreinte du contenu" . Ainsi, le contenu étreignant gagne.

En bout de ligne, faire en sorte que l'étiquette s'adapte au texte peut être aussi simple que de spécifier la contrainte de hauteur ou de largeur et de corriger la définition de cette priorité en association avec la contrainte de priorité de contournement du contenu de cet axe.

Je vais laisser faire l'équivalent pour la largeur comme exercice pour le lecteur!

Max MacLeod
la source
3
Merci beaucoup pour votre excellente explication! Enfin, je comprends la priorité du contenu.
kernix
Pourriez-vous expliquer également quelle est la priorité de résistance à la compression du contenu? Merci!
kernix
2
Excelente réponse Max! Existe-t-il un moyen de limiter le nombre de lignes fonctionnant de cette façon?
rafaeljuzo
7

Remarqué dans IOS7 sizeToFit ne fonctionnait pas non plus - peut-être que la solution peut vous aider aussi

[textView sizeToFit];
[textView layoutIfNeeded];
Nick Wood
la source
1
@Rambatino Cela fonctionne pour moi. Ajoutez layoutIfNeededquand vous en avez besoin setNeedsUpdateConstraints. Mais la situation pourrait être un peu différente.
Gon
Cela a fonctionné mieux pour moi lorsque j'ai fait un popover. J'avais besoin de faire la mise en page si besoin d'abord, cependant
Jason
4

Une autre option pour garantir que la valeur favoriteMaxLayoutWidth de l'étiquette est synchronisée avec la largeur de l'étiquette:

#import "MyLabel.h"

@implementation MyLabel

-(void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    // This appears to be needed for iOS 6 which doesn't seem to keep
    // label preferredMaxLayoutWidth in sync with its width, which 
    // means the label won't grow vertically to encompass its text if 
    // the label's width constraint changes.
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

@end
Monte Hurd
la source
4

Je pense que je devrais contribuer car il m'a fallu un certain temps pour trouver la bonne solution:

  • Le but est de laisser Auto Layout faire son travail sans jamais appeler sizeToFit (), nous le ferons en spécifiant les bonnes contraintes:
  • Spécifiez les contraintes d'espace en haut, en bas et au début / à la fin de votre UILabel
  • Définissez la propriété du nombre de lignes sur 0
  • Incrémenter la priorité de contenu à 1000
  • Réduisez la priorité de résistance à la compression du contenu à 500
  • Sur votre contrainte de conteneur inférieur, réduisez la priorité à 500

Fondamentalement, ce qui se passe est que vous dites à votre UILabel que même s'il a une contrainte de hauteur fixe, il peut faire casser la contrainte pour se rendre plus petit afin de serrer le contenu (si vous avez une seule ligne par exemple), mais il ne peut pas briser la contrainte pour l'agrandir.

ArturT
la source
3

Dans mon cas, je créais une sous-classe UIView qui contenait un UILabel (de longueur inconnue). Dans iOS7, le code était simple: définissez les contraintes, ne vous inquiétez pas de l'étreinte du contenu ou de la résistance à la compression, et tout fonctionnait comme prévu.

Mais dans iOS6, l'UILabel était toujours tronqué sur une seule ligne. Aucune des réponses ci-dessus n'a fonctionné pour moi. Les paramètres de contention du contenu et de résistance à la compression ont été ignorés. La seule solution qui empêchait l'écrêtage était d'inclure un PreferredMaxLayoutWidth sur l'étiquette. Mais je ne savais pas sur quoi définir la largeur préférée, car la taille de sa vue parente était inconnue (en effet, elle serait définie par le contenu).

J'ai enfin trouvé la solution ici . Comme je travaillais sur une vue personnalisée, je pouvais simplement ajouter la méthode suivante pour définir la largeur de mise en page préférée une fois que les contraintes avaient été calculées une fois, puis les recalculer:

- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}
sumizome
la source
C'était le scénario exact que j'avais… parfois c'est agréable de savoir que vous n'êtes pas seul.
daveMac
Adam, j'ai du mal avec ce que vous avez dit qui fonctionnait parfaitement dans ios7. J'ai une cellule personnalisée avec un uiview et un uilabel. Je peux faire en sorte que l'étiquette corresponde au contenu, mais pas la vue, quelles que soient les contraintes que je lui impose. Comment l'avez-vous fait fonctionner? Je jure que j'ai tout essayé mais ça ne prend pas la hauteur de l'étiquette
denikov
3

J'ai ajouté par UILabelprogramme et dans mon cas, c'était suffisant:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
mixel
la source
2

J'ai résolu avec xCode6 en mettant "Largeur préférée" sur Automatique et épingler le haut de l'étiquette, le début et la fin entrez la description de l'image ici

Kazzar
la source
0
UIFont *customFont = myLabel.font;
CGSize size = [trackerStr sizeWithFont:customFont
                             constrainedToSize:myLabel.frame.size // the size here should be the maximum size you want give to the label
                                 lineBreakMode:UILineBreakModeWordWrap];
float numberOfLines = size.height / customFont.lineHeight;
myLabel.numberOfLines = numberOfLines;
myLabel.frame = CGRectMake(258, 18, 224, (numberOfLines * customFont.lineHeight));
Swati
la source
0

J'ai également rencontré ce problème avec une sous-classe UIView qui contient un UILabel comme si ses éléments internes. Même avec la mise en page automatique et en essayant toutes les solutions recommandées, l'étiquette ne serrait tout simplement pas sa hauteur au texte. Dans mon cas, je veux que l'étiquette ne soit qu'une ligne.

Quoi qu'il en soit, ce qui a fonctionné pour moi était d'ajouter une contrainte de hauteur requise pour le UILabel et de le définir manuellement à la hauteur correcte lorsque intrinsicContentSize est appelé. Si vous n'avez pas le UILabel contenu dans un autre UIView, vous pouvez essayer de sous-classer UILabel et fournir une implémentation similaire en définissant d'abord la contrainte de hauteur, puis en retournant
[super instrinsicContentSize]; au lieu de [self.containerview intrinsiceContentSize]; comme je le fais ci-dessous, ce qui est spécifique à ma sous-classe UIView.

- (CGSize)intrinsicContentSize
{
    CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
    self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
    return [self.containerView intrinsicContentSize];

}

Fonctionne parfaitement maintenant sur iOS 7 et iOS 8.

n8tr
la source
Il convient de noter que si vous avez correctement configuré votre vue (c'est-à-dire que vos contraintes sont correctes et que vous avez tout configuré pendant les étapes correctes du cycle de vie de la vue), vous ne devriez jamais avoir à remplacer intrinsicContentSize. Le moteur d'exécution doit être en mesure de calculer cela en fonction de vos contraintes correctement configurées.
John Rogers
En théorie, cela devrait être correct, mais nous n'avons pas accès à toutes les API et implémentations privées d'Apple et elles ne sont pas infaillibles et ont des bogues dans leur code.
n8tr
... mais [super intrinsicContentSize] devrait s'en occuper, c'est ce que je dis. Si vous avez correctement implémenté votre mise en page automatique.
John Rogers
0

Une solution qui a fonctionné pour moi; Si votre UILabel a une largeur fixe, changez la contrainte de constant =à constant <=dans votre fichier d'interface

entrez la description de l'image ici

Alex Brown
la source
-1

Dans mon cas, lors de l'utilisation des étiquettes dans un UITableViewCell, l'étiquette à redimensionnerait mais la hauteur dépasserait la hauteur de la cellule du tableau. C'est ce qui a fonctionné pour moi. Je l'ai fait selon Max MacLeod, puis je me suis assuré que la hauteur de cellule était définie sur UITableViewAutomaticDimension.

Vous pouvez ajouter ceci dans votre init ou awakeFromNib,

self.tableView.rowHeight = UITableViewAutomaticDimension.  

Dans le storyboard, sélectionnez la cellule, ouvrez l'inspecteur de taille et assurez-vous que la hauteur de ligne est définie sur "Par défaut" en cochant la case "Personnalisé".

Il existe un problème dans la version 8.0 qui nécessite également qu'il soit défini dans le code.

Maria
la source