web-dev-qa-db-fra.com

Mise en page automatique iOS: problème avec UILabels dans une vue parent de redimensionnement

J'ai une vue qui contient uniquement un UILabel. Cette étiquette contient du texte multiligne. Le parent a une largeur variable qui peut être redimensionnée avec un mouvement panoramique. Mon problème est que lorsque je fais cela, le redimensionnement de l'UILabel ne recalcule pas sa hauteur de telle sorte que tout le contenu est toujours visible, il le coupe simplement.

J'ai réussi à le réparer avec un peu de hack mais c'est horriblement lent à exécuter:

- (void)layoutSubviews {

    CGSize labelSize = [self.labelDescription sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];

    if (self.constraintHeight) {
        [self removeConstraint:self.constraintHeight];
    }

    self.constraintHeight = [NSLayoutConstraint constraintWithItem:self.labelDescription attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:labelSize.height];

    [self addConstraint:self.constraintHeight];

    [super layoutSubviews];
}

Cela ne devrait-il pas se produire automatiquement avec la mise en page automatique?

MODIFIER

La structure de mon point de vue est la suivante:

UIScrollView
---> UIView
     ---> UILabel

Voici les contraintes sur UIScrollView:

<NSLayoutConstraint:0x120c4860 H:|-(>=32)-[DescriptionView:0x85c81c0]   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c48a0 H:|-(32@900)-[DescriptionView:0x85c81c0] priority:900   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c48e0 H:[DescriptionView:0x85c81c0(<=576)]>,
<NSLayoutConstraint:0x120c4920 H:[DescriptionView:0x85c81c0]-(>=32)-|   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c4960 H:[DescriptionView:0x85c81c0]-(32@900)-| priority:900   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x8301450 DescriptionView:0x85c81c0.centerX == UIScrollView:0x85db650.centerX>,

Voici les contraintes sur l'UIView:

<NSLayoutConstraint:0x85c4580 V:|-(0)-[UILabel:0x85bc7b0]   (Names: '|':DescriptionView:0x85c81c0 )>,
<NSLayoutConstraint:0x85c45c0 H:|-(0)-[UILabel:0x85bc7b0]   (Names: '|':DescriptionView:0x85c81c0 )>,
<NSLayoutConstraint:0x85c9f80 UILabel:0x85bc7b0.trailing == DescriptionView:0x85c81c0.trailing>,
<NSLayoutConstraint:0x85c9fc0 UILabel:0x85bc7b0.centerY == DescriptionView:0x85c81c0.centerY>

L'UILabel lui-même n'a aucune contrainte, à part l'épingler aux bords de son parent

30
simon

J'ai résolu ce problème après avoir signalé un bogue avec Apple. Le problème que le texte multiligne nécessite une approche en deux étapes de la mise en page et tout repose sur la propriété preferredMaxLayoutWidth

Voici le code pertinent qui doit être ajouté à un contrôleur de vue qui contient une étiquette multiligne:

- (void)viewWillLayoutSubviews
{
    // Clear the preferred max layout width in case the text of the label is a single line taking less width than what would be taken from the constraints of the left and right edges to the label's superview
    [self.label setPreferredMaxLayoutWidth:0.];
}

- (void)viewDidLayoutSubviews
{
    // Now that you know what the constraints gave you for the label's width, use that for the preferredMaxLayoutWidth—so you get the correct height for the layout
    [self.label setPreferredMaxLayoutWidth:[self.label bounds].size.width];

    // And then layout again with the label's correct height.
    [self.view layoutSubviews];
}
26
simon

D'accord, je l'ai finalement cloué. La solution consiste à définir le preferredMaxLayoutWidth dans viewDidLayoutSubviews, mais seulement après le premier tour de mise en page. Vous pouvez arranger cela simplement en renvoyant de manière asynchrone sur le thread principal. Alors:

- (void)viewDidLayoutSubviews {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.theLabel.preferredMaxLayoutWidth = self.theLabel.bounds.size.width;
    });
}

De cette façon, vous ne définissez pas preferredMaxLayoutWidth tant que après la largeur de l'étiquette a été correctement définie par ses contraintes liées à la vue d'ensemble.

Exemple de projet de travail ici:

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch23p673selfSizingLabel4/p565p579selfSizingLabel/ViewController.m

EDIT: Une autre approche! Sous-classe UILabel et remplacer layoutSubviews:

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

Le résultat est une étiquette à dimensionnement automatique - elle change automatiquement sa hauteur pour s'adapter à son contenu, peu importe comment sa largeur change (en supposant que sa largeur est modifiée par les contraintes/la disposition).

41
matt

Dans Xcode 6.1 pour iOS 7/8, j'ai pu faire fonctionner cela en définissant simplement preferredMaxLayoutWidth dans une méthode de définition qui est appelée sur ma vue pour afficher le texte de l'étiquette. Je suppose qu'il a été défini sur 0 pour commencer. Exemple ci-dessous, où self.teachPieceLabel est le libellé. L'étiquette est câblée avec des contraintes aux côtés d'autres étiquettes dans une vue dans Interface Builder.

- (void)setTeachPieceText:(NSString *)teachPieceText {
      self.teachPieceLabel.text = teachPieceText;
      [self.teachPieceLabel setPreferredMaxLayoutWidth:[self.teachPieceLabel bounds].size.width];
}
4
Evan R