iOS: UILabel multiligne dans la mise en page automatique

183

J'ai du mal à essayer d'obtenir un comportement de mise en page très basique avec la mise en page automatique. Mon contrôleur de vue ressemble à ceci dans IB:

entrez la description de l'image ici

L'étiquette du haut est l'étiquette du titre, je ne sais pas combien de lignes ce sera. J'ai besoin de l'étiquette de titre pour afficher toutes les lignes de texte. J'ai également besoin que les deux autres étiquettes et la petite image soient disposées juste en dessous du titre, quelle que soit sa hauteur. J'ai défini des contraintes d'espacement vertical entre les étiquettes et la petite image, ainsi qu'une contrainte d'espacement en haut entre l'étiquette de titre et sa super-vue et une contrainte d'espacement en bas entre la petite image et sa super-vue. Le UIView blanc n'a pas de contrainte de hauteur, il doit donc s'étirer verticalement pour contenir ses sous-vues. J'ai défini le nombre de lignes de l'étiquette de titre sur 0.

Comment puis-je faire redimensionner l'étiquette de titre pour qu'elle corresponde au nombre de lignes requis par la chaîne? Je crois comprendre que je ne peux pas utiliser les méthodes setFrame car j'utilise la mise en page automatique. Et je dois utiliser la mise en page automatique car j'ai besoin que ces autres vues restent en dessous de l'étiquette de titre (d'où les contraintes).

Comment puis-je y arriver?

James Harpe
la source
3
Je me bats également avec un problème similaire. Mais j'ai toujours du mal à faire en sorte que l'étiquette supérieure ajuste sa hauteur de manière dynamique pour s'adapter au contenu. Comment avez-vous réussi cela?
jbandi
1
Veuillez envisager de marquer la réponse de @ mwhuss comme étant acceptée.
jemmons
1
Avez-vous obtenu le résultat souhaité?
Aniruddha
vérifiez ceci, vous n'avez pas besoin d'ajouter une seule ligne de code stackoverflow.com/a/36862795/4910767
Badal Shah

Réponses:

254

L'utilisation -setPreferredMaxLayoutWidthsur UILabelet la mise en page automatique devrait gérer le reste.

[label setPreferredMaxLayoutWidth:200.0];

Reportez-vous à la documentation UILabel sur favoriteMaxLayoutWidth .

Mettre à jour:

Il suffit de définir la heightcontrainte dans le storyboard sur Greater than or equal to, pas besoin de setPreferredMaxLayoutWidth.

mwhuss
la source
8
Nice one, de toute façon pour faire cela dans le constructeur d'interface?
Sjoerd Perfors
12
Définissez
10
Rappel: n'oubliez pas de définir la propriété numberOfLines.
Li Fumin
2
Oui, utilisez ce que vous voulez que la largeur maximale soit.
mwhuss
4
Voir stackoverflow.com/a/29180323/1529675 pour savoir comment déterminer preferredMaxLayoutWidthet voir devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html pour un bref mais informatif écrit, complet avec GIF animés (pas mon écriture, je l'ai trouvé via Google)
tboyce12
213

Développez le nombre de lignes de votre jeu d'étiquettes à 0 et, plus important encore, pour la mise en page automatique, définissez la hauteur sur> = x. La mise en page automatique fera le reste. Vous pouvez également contenir vos autres éléments basés sur l'élément précédent pour positionner correctement ensuite.

mise en page automatique

Charlie Wu
la source
Si cette méthode ne fonctionne pas pour vous, veuillez consulter la réponse de Cory Imdieke pour vous assurer que les vues suivantes n'empêchent pas la mise en page automatique de redimensionner la vue multiligne (éventuellement).
Tom Howard
1
C'est la seule réponse qui a fonctionné pour moi dans une cellule de vue tableau.
Fonctionne
Cela n'a pas fonctionné pour moi avec un jeu de largeur maximum préféré explicite, alors assurez-vous qu'il est réglé sur automatique dans IB lorsque vous utilisez cette technique. Cheers
jmc
La définition de la largeur maximale automatique n'est disponible que dans iOS8. D'après ce que je comprends, cela ajustera automatiquement la hauteur et, d'après mon expérience, vous n'avez pas besoin de définir une contrainte de hauteur pour que cela fonctionne. Cela a fonctionné pour moi en utilisant une largeur explicite.
Tony
1
Cela n'a pas fonctionné pour moi. Je crois que stackoverflow.com/a/13032251/1529675 est la bonne réponse.
tboyce12
48

Source: http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html

Taille du contenu intrinsèque du texte multiligne

La taille du contenu intrinsèque de UILabel et NSTextField est ambiguë pour le texte multiligne. La hauteur du texte dépend de la largeur des lignes, qui reste à déterminer lors de la résolution des contraintes. Afin de résoudre ce problème, les deux classes ont une nouvelle propriété appelée favoriteMaxLayoutWidth, qui spécifie la largeur de ligne maximale pour calculer la taille du contenu intrinsèque.

Puisque nous ne connaissons généralement pas cette valeur à l'avance, nous devons adopter une approche en deux étapes pour y parvenir. Nous laissons d'abord la mise en page automatique faire son travail, puis nous utilisons le cadre résultant dans la passe de mise en page pour mettre à jour la largeur maximale préférée et déclencher à nouveau la mise en page.

- (void)layoutSubviews
{
    [super layoutSubviews];
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
    [super layoutSubviews];
}

Le premier appel à [super layoutSubviews] est nécessaire pour que l'étiquette obtienne son jeu de cadres, tandis que le deuxième appel est nécessaire pour mettre à jour la mise en page après la modification. Si nous omettons le deuxième appel, nous obtenons une erreur NSInternalInconsistencyException, car nous avons apporté des modifications dans la passe de mise en page qui nécessitent la mise à jour des contraintes, mais nous n'avons pas déclenché à nouveau la mise en page.

Nous pouvons également le faire dans une sous-classe d'étiquettes elle-même:

@implementation MyLabel
- (void)layoutSubviews
{
    self.preferredMaxLayoutWidth = self.frame.size.width;
    [super layoutSubviews];
}
@end

Dans ce cas, nous n'avons pas besoin d'appeler d'abord [super layoutSubviews], car lorsque layoutSubviews est appelé, nous avons déjà un cadre sur l'étiquette elle-même.

Pour effectuer cet ajustement à partir du niveau du contrôleur de vue, nous nous connectons à viewDidLayoutSubviews. À ce stade, les cadres de la première passe de mise en page automatique sont déjà définis et nous pouvons les utiliser pour définir la largeur maximale préférée.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
    [self.view layoutIfNeeded];
}

Enfin, assurez-vous que vous n'avez pas de contrainte de hauteur explicite sur l'étiquette qui a une priorité plus élevée que la priorité de résistance à la compression du contenu de l'étiquette. Sinon, il l'emportera sur la hauteur calculée du contenu. Assurez-vous de vérifier toutes les contraintes qui peuvent affecter la hauteur de l'étiquette.

Anton Matosov
la source
merci merci merci non seulement pour le code, mais surtout pour l'explication et les exemples sur la façon de le faire non seulement dans une sous-classe, mais aussi à partir du contrôleur de vue.
djibouti33
J'essaye ceci dans mon contrôleur de vue, et viewDidLayoutSubviews est appelé après viewWillAppear et viewDidAppear. Après viewDidAppear, il y a un flash lorsque les étiquettes sont correctement redimensionnées. y-a-t-il un moyen d'éviter ça? existe-t-il un moyen pour que le redimensionnement soit terminé avant que l'utilisateur ne voie quoi que ce soit? dans mon cas, mes étiquettes multilignes sont dans une tableHeaderView, et je redimensionne donc également la hauteur de la vue d'en-tête en fonction des étiquettes à l'aide de cet exemple ( stackoverflow.com/questions/20982558/… )
djibouti33
@ djibouti33 pouvez-vous s'il vous plaît fournir un exemple de code minimum (par exemple au format du dépôt github) afin que je puisse facilement vérifier votre problème?
Anton Matosov
Merci @Anton. Notre conception est depuis passée d'un UITableView à un UICollectionView, et heureusement je n'ai pas vu ce comportement. Si je peux accéder à mon historique git pour créer un petit exemple de projet, je vous le ferai savoir.
djibouti33
Un problème pour moi depuis que j'ai commencé à courir sur une sim iOS 9, mais lorsque j'ai testé sur iOS 8, le problème a commencé. Je dois définir la largeur maximale manuellement.
naz
17

Je me battais juste avec ce scénario exact, mais avec quelques vues supplémentaires qui devaient être redimensionnées et abaissées si nécessaire. Cela me rendait fou, mais je l'ai finalement compris.

Voici la clé: Interface Builder aime ajouter des contraintes supplémentaires lorsque vous ajoutez et déplacez des vues et vous ne le remarquerez peut-être pas. Dans mon cas, j'avais une vue à mi-chemin qui avait une contrainte supplémentaire qui spécifiait la taille entre elle et son superview, l'épinglant essentiellement à ce point. Cela signifiait que rien au-dessus ne pouvait être redimensionné plus grand car cela irait à l'encontre de cette contrainte.

Un moyen simple de savoir si tel est le cas consiste à essayer de redimensionner l'étiquette manuellement. IB vous permet-il de le cultiver? Si tel est le cas, les libellés ci-dessous se déplacent-ils comme prévu? Assurez-vous d'avoir coché ces deux éléments avant de redimensionner pour voir comment vos contraintes déplaceront vos vues:

Menu IB

Si la vue est bloquée, suivez les vues qui se trouvent en dessous et assurez-vous que l'une d'elles n'a pas d'espace supérieur pour superviser la contrainte. Ensuite, assurez-vous simplement que votre option de nombre de lignes pour l'étiquette est définie sur 0 et qu'elle devrait s'occuper du reste.

Cory Imdieke
la source
Oui, après avoir beaucoup joué avec, j'ai pu obtenir l'effet que je voulais. Il suffit de réfléchir très attentivement aux contraintes que l'on souhaite. Le fait qu'IB ajoute constamment des contraintes auxquelles vous ne vous attendez pas n'aide pas.
James Harpe
5

Je trouve que vous avez besoin des éléments suivants:

  • Une contrainte majeure
  • Une contrainte principale (par exemple côté gauche)
  • Une contrainte de fin (par exemple côté droit)
  • Définissez la priorité de respect du contenu, horizontale à faible, de sorte qu'il remplisse l'espace donné si le texte est court.
  • Réglez la résistance à la compression du contenu, horizontale à faible, pour qu'elle s'enroule au lieu d'essayer de s'élargir.
  • Définissez le nombre de lignes sur 0.
  • Définissez le mode de saut de ligne sur word wrap.
Chris
la source
Cela devrait fonctionner dans xcode9. Je n'ai rien eu à faire avec la contrainte de hauteur d'étiquette (je n'en ai pas): cela fonctionne juste hors de la boîte ces derniers temps une fois que l'étiquette aura été contrainte (fin, début, haut et bas). uikit fait le reste
Anton Tropashko
La priorité du contenu était mon problème. Merci de m'avoir fait connaître cette propriété ainsi que la résistance à la compression. Vous m'avez aidé à résoudre un problème de plusieurs heures.
David Gay
0

Aucune des différentes solutions trouvées dans les nombreux sujets sur le sujet n'a fonctionné parfaitement pour mon cas (x étiquettes multilignes dynamiques dans des cellules de vue de tableau dynamique).

J'ai trouvé un moyen de le faire:

Après avoir défini les contraintes sur votre étiquette et mis sa propriété multiligne à 0, créez une sous-classe de UILabel; J'ai appelé le mien AutoLayoutLabel:

@implementation AutoLayoutLabel

- (void)layoutSubviews{
    [self setNeedsUpdateConstraints];
    [super layoutSubviews];
    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
}

@end
Tanguy G.
la source
0

J'ai un UITableViewCell qui a une étiquette d'habillage de texte. J'ai travaillé l'habillage de texte comme suit.

1) Définissez les contraintes UILabel comme suit.

entrez la description de l'image ici

2) Réglez no. de lignes à 0.

3) Ajout de la contrainte de hauteur UILabel à UITableViewCell.

@IBOutlet weak var priorityLabelWidth: NSLayoutConstraint!

4) Sur UITableViewCell:

priorityLabel.sizeToFit()
priorityLabelWidth.constant = priorityLabel.intrinsicContentSize().width+5
AG
la source
-12

Une façon de faire cela ... À mesure que la longueur du texte augmente, essayez de modifier (réduire) la taille de la police du texte de l'étiquette en utilisant

Label.adjustsFontSizeToFitWidth = YES;
user1662392
la source
1
Je veux que les différentes instances du contrôleur de vue aient une apparence cohérente, donc je ne veux pas changer la taille de la police.
James Harpe