web-dev-qa-db-fra.com

UILabel n'emballe pas le texte correctement parfois (mise en page automatique)

J'ai une configuration uilabel dans une vue. Il n'a pas de contrainte de largeur, mais sa largeur est plutôt déterminée par une contrainte principale de la vignette et une contrainte de fin sur le bord de la vue.

enter image description here

L'étiquette est définie pour avoir 0 lignes et pour le retour à la ligne. Si j'ai bien compris, cela devrait entraîner la croissance du cadre de uilabel, et c'est même parfois le cas. (Avant la mise en page automatique, je calculais et mettais à jour le cadre de l'étiquette en code).

Le résultat est que cela fonctionne correctement dans certains cas et pas dans d'autres. Voir que la plupart des cellules fonctionnent correctement ici, mais la dernière cellule semble être trop grande. En fait, c'est la bonne taille. Le titre "Test de vérification du smog de Fair Oaks" se termine en réalité par "Seulement". Donc, mon calcul pour la taille de la cellule est exact, il devrait être de cette taille. Cependant, l’étiquette n’enveloppe pas le texte pour une raison quelconque. La largeur du cadre ne va pas à droite, ce n'est donc pas le problème.

enter image description here

Que se passe-t-il? C’est cohérent à 100%, toujours sur cette cellule et non sur celles au-dessus, ce qui me fait penser que cela est lié à la taille du texte, et UILabel ne remet pas en forme le texte une fois cette vue ajoutée à la cellule le rend réellement plus petit en largeur).

Des pensées?

Quelques informations supplémentaires

La hauteur des cellules est calculée à partir d'une cellule d'échantillon que je crée et stocke dans une variable statique:

- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (self.items.count == 0) {
        return 60;
    }
    static TCAnswerBuilderCell *cell = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred,
                  ^{
                      // get a sample cellonce
                      cell = [tableView dequeueReusableCellWithIdentifier:TC_ANSWER_BUILDER_CELL];
                  });
    [cell configureCellWithThirdPartyObject:self.items[indexPath.row]];
    return [cell heightForCellWithTableWidth:self.tableView.frame.size.width];
}

Je configure la cellule avec mon objet de données à la volée, puis appelle une méthode que j'ai sur elle qui calcule la hauteur de la cellule avec une largeur de tableau donnée (ne peut pas toujours compter sur le cadre de la cellule étant initialement correct).

Ceci à mon tour appelle une méthode de hauteur à mon avis, puisque c’est là que vit réellement le label:

- (CGFloat)heightForCellWithTableWidth:(CGFloat)tableWidth {
    // subtract 38 from the constraint above
    return [self.thirdPartyAnswerView heightForViewWithViewWidth:tableWidth - 38];
}

Cette méthode détermine la hauteur en déterminant la largeur correcte de l'étiquette, puis en effectuant un calcul:

- (CGFloat)heightForViewWithViewWidth:(CGFloat)viewWidth {
    CGFloat widthForCalc = viewWidth - self.imageFrameLeadingSpaceConstraint.constant - self.thumbnailFrameWidthConstraint.constant - self.titleLabelLeadingSpaceConstraint.constant;
    CGSize size = [self.titleLabel.text sizeWithFont:self.titleLabel.font constrainedToSize:CGSizeMake(widthForCalc, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
    CGFloat returnHeight = self.frame.size.height - self.titleLabel.frame.size.height + size.height;
    CGFloat height = returnHeight < self.frame.size.height ? self.frame.size.height : returnHeight;
    return height;
}

Cela fonctionne à 100% correctement.

Les cellules sont évidemment créées dans cellForRowAtIndexPath et immédiatement configurées:

if (self.items.count > 0) {
    TCAnswerBuilderCell *cell = [tableView dequeueReusableCellWithIdentifier:TC_ANSWER_BUILDER_CELL forIndexPath:indexPath];
    [cell configureCellWithThirdPartyObject:self.items[indexPath.row]];
    return cell;
}

Dans la configuration de la cellule, ma vue est chargée depuis un nib (elle est réutilisée ailleurs, c'est pourquoi elle n'est pas directement dans la cellule). La cellule l'ajoute comme suit:

- (void) configureCellWithThirdPartyObject:(TCThirdPartyObject *)object {
    self.detailDisclosureImageView.hidden = NO;
    if (!self.thirdPartyAnswerView) {
        self.thirdPartyAnswerView = [TCThirdPartyAPIHelper thirdPartyAnswerViewForThirdPartyAPIServiceType:object.thirdPartyAPIType];
        self.thirdPartyAnswerView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.contentView addSubview:self.thirdPartyAnswerView];
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[_thirdPartyAnswerView]-38-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:NSDictionaryOfVariableBindings(_thirdPartyAnswerView)]];
    }
    [self.thirdPartyAnswerView configureViewForThirdPartyObject:object forViewStyle:TCThirdPartyAnswerViewStyleSearchCell];
}

Enfin, ma configuration de vue ressemble à ceci:

- (void) configureViewForThirdPartyObject:(TCTPOPlace *)object forViewStyle:(TCThirdPartyAnswerViewStyle) style {
    self.titleLabel.text = object.name;
    self.addressLabel.text = [NSString stringWithFormat:@"%@, %@, %@", object.address, object.city, object.state];
    self.ratingsLabel.text = [NSString stringWithFormat:@"%d Ratings", object.reviewCount];
    NSString *ratingImageName = [NSString stringWithFormat:@"Yelp_star_rating_%.1f.png", object.rating];
    UIImage *ratingsImage = [UIImage imageNamed:ratingImageName];
    if (ratingsImage) {
        self.ratingImageView.image = ratingsImage;
    }
    if (object.imageUrl) {
        [self.thumbnailImageView setImageWithURL:[NSURL URLWithString:object.imageUrl] completed:nil];
    }
}

Une sorte de solution, mais je ne comprends pas pourquoi

  1. Ma sous-vue a été conçue en largeur 320, mais elle n’a pas de contrainte de largeur.
  2. La sous-vue a été ajoutée à la cellule, mais avec des contraintes horizontales ressemblant à ceci:
    • @"|[_thirdPartyAnswerView]-38-|"
  3. La vue a été configurée immédiatement après avoir été ajoutée à la cellule, ce qui signifie que le texte du libellé titleLabel a été défini à ce moment-là.
  4. Pour une raison quelconque, le texte a été présenté comme si la vue en contenait 320 au lieu de 282.
  5. L'étiquette n'a jamais été mise à jour, même si le cadre de la sous-vue a été mis à jour à 282, et l'étiquette comportait des contraintes lui permettant de conserver une taille correcte.
  6. La modification de la taille de la vue dans la bibliothèque xib sur 282 corrigeait le problème, car l'étiquette avait la bonne taille pour commencer.

Je ne comprends toujours pas pourquoi le libellé ne s'affiche pas de nouveau après la mise à jour de la taille de la vue parent lorsque celle-ci comporte des contraintes de début et de fin.

Résolu

Voir la réponse de Matt ci-dessous: https://stackoverflow.com/a/15514707/287403

Au cas où vous ne liriez pas le commentaire, le principal problème était que je configurais inconsciemment preferredMaxLayoutWidth via IB lorsque je concevais une vue sur une largeur plus grande que celle affichée (dans certains cas). preferredMaxLayoutWidth est ce qui est utilisé pour déterminer où le texte est renvoyé. Ainsi, même si ma vue et titleLabel ont été correctement redimensionnés, la variable preferredMaxLayoutWidth était toujours à l’ancienne valeur et entraînait un retour à la ligne à des points inattendus. En définissant plutôt titleLabel sur sa taille automatique (= dans IB), et la mise à jour de la variable preferredMaxLayoutWidth de manière dynamique dans layoutSubviews avant d'appeler super était la clé. Merci Matt!

40
Bob Spryn

Je suis quelqu'un qui a écrit une application qui utilise la suppression automatique de cinq étiquettes dans une cellule d'un tableau dont les cellules ont des hauteurs différentes. Je vais donc suggérer que la raison pour laquelle vous rencontrez des problèmes est peut-être que vos contraintes sous-déterminent la présentation, c'est-à-dire que vous avez une disposition ambiguë des éléments de la cellule. Je ne peux pas tester cette hypothèse car je ne vois pas vos contraintes. Mais vous pouvez facilement vérifier (je pense) en utilisant po [[UIWindow keyWindow] _autolayoutTrace] lorsqu’il est mis en pause dans le débogueur.

J'ai aussi une autre suggestion (désolée de vous lancer des choses): assurez-vous que vous définissez la variable preferredMaxLayoutWidth de l'étiquette. Ceci est crucial car c'est la largeur à laquelle l'étiquette cessera de croître horizontalement et commencera à croître verticalement.

42
matt

J'ai eu le même problème et résolu en utilisant une suggestion de cette réponse . Dans une sous-classe de UILabel, j'ai placé ce code:

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

Je ne comprends pas pourquoi ce n'est pas le comportement par défaut d'UILabel, ou du moins pourquoi vous ne pouvez pas simplement l'activer via un indicateur.

Je suis un peu préoccupé par le fait que preferredMaxLayoutWidth est défini au milieu du processus de présentation, mais je ne vois pas de solution simple à ce problème.

13
phatmann

Vérifiez également que vous passez des nombres intégraux à vos contraintes de présentation dans le code. 

Pour moi, après quelques calculs (par exemple, convertPoint: toView :), je passais quelque chose comme 23.99999997, ce qui a finalement abouti à une étiquette de 2 lignes s’affichant comme une ligne (si son cadre semblait être calculé correctement) . Dans mon cas, CGRectIntegral a fait le tour!

Les erreurs d'arrondis pourraient te tuer :)

0
Sergiu Todirascu