boundingRectWithSize pour NSAttributedString renvoyant une taille incorrecte


151

J'essaie d'obtenir le rect pour une chaîne attribuée, mais l'appel boundingRectWithSize ne respecte pas la taille que je passe et renvoie un rect avec une seule hauteur de ligne par opposition à une grande hauteur (c'est une longue chaîne). J'ai expérimenté en passant une très grande valeur pour la hauteur et aussi 0 comme dans le code ci-dessous, mais le rect renvoyé est toujours le même.

CGRect paragraphRect = [attributedText boundingRectWithSize:CGSizeMake(300,0.0)
  options:NSStringDrawingUsesDeviceMetrics
  context:nil];

Est-ce cassé ou dois-je faire autre chose pour qu'il renvoie un rect pour le texte enveloppé?


5
Avez-vous un style de paragraphe avec troncature / découpage lineBreakMode?
danyowdee

1
si vous lisez ceci parce que UILabel mesure / enveloppe à une largeur incorrecte, jetez un œil à stackoverflow.com/questions/46200027/… . plus précisément, en définissant NSAllowsDefaultLineBreakStrategy sur false au lancement de l'application.
eric le

Réponses:


312

Il semble que vous ne fournissiez pas les bonnes options. Pour emballer les étiquettes, fournissez au moins:

CGRect paragraphRect =
  [attributedText boundingRectWithSize:CGSizeMake(300.f, CGFLOAT_MAX)
  options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
  context:nil];

Remarque: si la largeur du texte d'origine est inférieure à 300 f, il n'y aura pas de retour à la ligne, alors assurez-vous que la taille reliée est correcte, sinon vous obtiendrez toujours des résultats erronés.


25
Tout le monde doit regarder cette réponse, car cela fonctionne. Une documentation plus claire est peut-être nécessaire pour cette méthode et les chaînes attribuées en général; ce n'est pas évident où vous devez regarder.
macserv

31
Attention! Ça ne marche pas toujours! Parfois, la largeur renvoyée est supérieure à la largeur du paramètre de taille. La documentation de la méthode Even Apples déclare: "Les contraintes que vous spécifiez dans le paramètre size sont un guide pour le moteur de rendu pour savoir comment dimensionner la chaîne. Cependant, le rectangle englobant réel renvoyé par cette méthode peut être plus grand que les contraintes si un espace supplémentaire est nécessaire pour rendre la chaîne entière. "
Klaas le

75
L'API pour NSAttributedString et boundingRectWithSize est absolument choquante.
malhal

27
Un autre inconvénient est que la hauteur (et probablement la largeur) renvoyée dans le paragraphRect est presque toujours une valeur fractionnaire. Donc, il peut sortir en disant 141,3 comme hauteur. Vous devez utiliser le résultat de ceilf(paragraphRect.size.height)pour qu'il arrondisse. J'oublie cela tout le temps et je me demande pourquoi mes étiquettes sont toujours coupées.
jamone

39
boundingRectWithSize:J'emballe toujours mes calculs dans CGRectIntegral () qui CGRectIntegral rounds the rectangle’s origin downward and its size upward to the nearest whole integers, dans ce cas, arrondira la hauteur et la largeur pour garantir qu'aucun écrêtage ne se produit si la hauteur ou la largeur est une valeur fractionnaire.
runmad

47

Pour une raison quelconque, boundingRectWithSize renvoie toujours une taille incorrecte. J'ai trouvé une solution. Il existe une méthode pour UItextView -sizeThatFits qui renvoie la taille appropriée pour l'ensemble de texte. Ainsi, au lieu d'utiliser boundingRectWithSize, créez un UITextView, avec un cadre aléatoire, et appelez son sizeThatFits avec la largeur et la hauteur CGFLOAT_MAX respectives. Il renvoie la taille qui aura la bonne hauteur.

   UITextView *view=[[UITextView alloc] initWithFrame:CGRectMake(0, 0, width, 10)];   
   view.text=text;
   CGSize size=[view sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
   height=size.height; 

Si vous calculez la taille dans une boucle while, n'oubliez pas d'ajouter cela dans un pool de libération automatique, car il y aura n nombre d'UITextView créé, la mémoire d'exécution de l'application augmentera si nous n'utilisons pas autoreleasepool.


1
Cela marche. Les autres façons dont j'ai essayé, je ne pourrais jamais me rendre au travail. Je pense que mon problème vient de la complexité de la chaîne attribuée que j'attribuais. Il provenait d'un fichier RTF et utilisait une variété de polices, d'espacement des lignes, même d'ombres, et malgré l'utilisation de UsesLineFragmentOrigin et UsesFontLeading, je ne pouvais jamais obtenir un résultat suffisamment grand, et le problème était pire plus la chaîne attribuée était longue. Je suppose que pour des chaînes relativement simples, la méthode boundingRectWithSize peut fonctionner. Ils ont vraiment besoin d'une meilleure méthode pour travailler avec ça, imo.
John Bushnell

ne pensez-vous pas qu'il est exagéré de créer UITextViewautant de fois que la heightForRowAtIndexPathméthode est appelée? il faut du temps
János

Je suis d'accord avec vous @ János, mais c'est la seule solution qui a fonctionné pour moi.
shoan le

btw j'ai demandé une solution appropriée: stackoverflow.com/questions/32495744/…
János

c'est la seule solution qui fonctionne. Ce problème a 9 ans et Apple ne veut apparemment pas résoudre cela ou leurs ingénieurs sont trop faibles pour trouver une solution à cela. Nous parlons ici d'iOS 11, toujours porteur du même bug.
Canard

31

Ed McManus a certainement fourni une clé pour que cela fonctionne. J'ai trouvé un cas qui ne fonctionne pas

UIFont *font = ...
UIColor *color = ...
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                     font, NSFontAttributeName,
                                     color, NSForegroundColorAttributeName,
                                     nil];

NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString: someString attributes:attributesDictionary];

[string appendAttributedString: [[NSAttributedString alloc] initWithString: anotherString];

CGRect rect = [string boundingRectWithSize:constraint options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];

rect n'aura pas la bonne hauteur. Notez qu'une autre chaîne (qui est ajoutée à la chaîne ) a été initialisée sans dictionnaire d'attributs. Il s'agit d'un initialiseur légitime pour anotherString mais boundingRectWithSize: ne donne pas une taille précise dans ce cas.


33
Je vous remercie! Il s'avère que CHAQUE partie d'un NSAttributedString doit avoir un dictionnaire défini avec au moins NSFontAttributeName et NSForegroundColorAttributeName définis, si vous souhaitez que boundingRectWithSize fonctionne réellement! Je ne vois cela documenté nulle part.
Ben Wheeler

3
Notez qu'il apparaît également que les différents NSAttributedStrings qui sont combinés pour en faire un seul doivent tous utiliser le même dictionnaire (et donc la même police) pour que boundingRectWithSize fonctionne correctement!
Ben Wheeler

1
Cette solution est très similaire à celle que j'utilise pour ma catégorie UILabel pour "shrink to fit". La clé pour que cela fonctionne était de créer le rectangle de délimitation avec une hauteur FLT_MAX. Sans cela, je semblerais obtenir un rectangle pour une seule ligne.
dre1138

+1s tout autour. Cela m'a vraiment eu, alors merci d'avoir travaillé sur les gars.
Wex

J'ai eu ce problème. J'ai utilisé des espaces et des retours à la ligne NSMutableAttributedStringsans attributs et je me trompais de taille.
vbezhenar

28

Ma décision finale après une longue enquête: la
- boundingRectWithSizefonction renvoie la taille correcte pour une séquence ininterrompue de caractères uniquement! Dans le cas où la chaîne contient des espaces ou autre chose (appelé par Apple "Certains des glyphes") - il est impossible d'obtenir la taille réelle du rect nécessaire pour afficher le texte!
J'ai remplacé les espaces dans mes chaînes par des lettres et j'ai immédiatement obtenu un résultat correct.

Apple dit ici: https://developer.apple.com/documentation/foundation/nsstring/1524729-boundingrectwithsize

"Cette méthode renvoie les limites réelles des glyphes dans la chaîne. Certains des glyphes (espaces, par exemple) sont autorisés à chevaucher les contraintes de mise en page spécifiées par la taille transmise, donc dans certains cas, la valeur de largeur du composant de taille de la valeur renvoyée CGRectpeut dépasser la valeur de largeur du paramètre de taille. "

Il est donc nécessaire de trouver un autre moyen de calculer le droit réel ...


Après un long processus d'enquête, la solution a finalement été trouvée !!! Je ne suis pas sûr que cela fonctionnera bien pour tous les cas liés à UITextView, mais la chose principale et importante a été détectée!

boundingRectWithSizefunction ainsi que CTFramesetterSuggestFrameSizeWithConstraints(et de nombreuses autres méthodes) calculera la taille et la partie de texte correctement lorsque le rectangle correct est utilisé. Par exemple - UITextViewa textView.bounds.size.width- et cette valeur n'est pas un rectangle réel utilisé par le système lors du dessin de texte UITextView.

J'ai trouvé un paramètre très intéressant et effectué un calcul simple en code:

CGFloat padding = textView.textContainer.lineFragmentPadding;  
CGFloat  actualPageWidth = textView.bounds.size.width - padding * 2;

Et la magie fonctionne - tous mes textes calculés correctement maintenant! Prendre plaisir!


1
oui, j'ai remarqué que aussi, il ne garde pas les contraintes de largeur, il sort toujours plus grand. Ce n'est vraiment pas cool, ils ont désapprouvé la méthode de travail et maintenant nous devons faire face à cette merde.
Boris Gafurov

1
Vous monsieur, êtes mon nouveau héros! Cela me rendait fou. Je suis en train de définir les insertions textContainers, donc j'ai dû utiliser textView.textContainer.size.width au lieu de textView.bounds.size.witdh.
GCBenson

Je ne pouvais pas voter assez pour cela. Tellement bizarre que les espaces ne sont pas pris en compte dans les calculs de délimitation.
Chase Holland

1
Le remplacement des espaces par un caractère factice comme "3" renvoie la hauteur exacte
Abuzar Amin

1
cela a sauvé ma carrière et ma relation avec l'équipe QA. Je te dois un homme de bière !!
lucaslt89

14

Version Swift quatre

let string = "A great test string."
let font = UIFont.systemFont(ofSize: 14)
let attributes: [NSAttributedStringKey: Any] = [.font: font]
let attributedString = NSAttributedString(string: string, attributes: attributes)
let largestSize = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)

//Option one (best option)
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
let textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(), nil, largestSize, nil)

//Option two
let textSize = (string as NSString).boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], attributes: attributes, context: nil).size

//Option three
let textSize = attributedString.boundingRect(with: largestSize, options: [.usesLineFragmentOrigin , .usesFontLeading], context: nil).size

Mesurer le texte avec CTFramesetter fonctionne mieux car il fournit des tailles entières et gère bien les emoji et autres caractères Unicode.


10

Je n'ai eu de chance avec aucune de ces suggestions. Ma chaîne contenait des puces Unicode et je soupçonne qu'elles causaient des problèmes dans le calcul. J'ai remarqué que UITextView gérait bien le dessin, alors j'ai cherché à tirer parti de son calcul. J'ai fait ce qui suit, ce qui n'est probablement pas aussi optimal que les méthodes de dessin NSString, mais au moins c'est précis. C'est aussi légèrement plus optimal que d'initialiser un UITextView juste pour appeler -sizeThatFits:.

NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(width, CGFLOAT_MAX)];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:formattedString];
[textStorage addLayoutManager:layoutManager];

const CGFloat formattedStringHeight = ceilf([layoutManager usedRectForTextContainer:textContainer].size.height);

Ce n'était peut-être pas les points Unicode, mais peut-être des espaces à la fin de votre chaîne. Voir la réponse ci-dessus: stackoverflow.com/a/25941139/337934 .
SamB

9

Si vous souhaitez obtenir un cadre de délimitation en tronquant la queue, cette question peut vous aider.

CGFloat maxTitleWidth = 200;

NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = NSLineBreakByTruncatingTail;

NSDictionary *attributes = @{NSFontAttributeName : self.textLabel.font,
                             NSParagraphStyleAttributeName: paragraph};

CGRect box = [self.textLabel.text
              boundingRectWithSize:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)
              options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
              attributes:attributes context:nil];

9

Il s'avère que CHAQUE partie d'un NSAttributedString doit avoir un dictionnaire défini avec au moins NSFontAttributeName et NSForegroundColorAttributeName définis, si vous souhaitez que boundingRectWithSize fonctionne réellement!

Je ne vois cela documenté nulle part.


Merci, c'était la solution pour moi, sauf que j'ai trouvé que seul NSFontAttributeName était nécessaire, pas NSForegroundColorAttributeName
Marmoy

7

@warrenm Désolé de dire que la méthode du framesetter n'a pas fonctionné pour moi.

Cette fonction peut nous aider à déterminer la taille de trame nécessaire pour une plage de chaînes d'un NSAttributedString dans le SDK iphone / Ipad pour une largeur donnée:

Il peut être utilisé pour une hauteur dynamique de cellules UITableView

- (CGSize)frameSizeForAttributedString:(NSAttributedString *)attributedString
{
    CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
    CGFloat width = YOUR_FIXED_WIDTH;

    CFIndex offset = 0, length;
    CGFloat y = 0;
    do {
        length = CTTypesetterSuggestLineBreak(typesetter, offset, width);
        CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(offset, length));

        CGFloat ascent, descent, leading;
        CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

        CFRelease(line);

        offset += length;
        y += ascent + descent + leading;
    } while (offset < [attributedString length]);

    CFRelease(typesetter);

    return CGSizeMake(width, ceil(y));
}

Merci à HADDAD ISSA >>> http://haddadissa.blogspot.in/2010/09/compute-needed-heigh-for-fixed-width-of.html


Ne fonctionne pour moi sur aucun appareil (iPhone 4 et 5). Tous deux sur appareil avec iOS7.1.2 et Simulateur 4s avec iOS8.3 :(
sanjana

Cela nécessite une importation?
jose920405

@KaranAlangat yes#import <CoreText/CoreText.h>
jose920405

7

J'ai constaté que la solution préférée ne gère pas les sauts de ligne.

J'ai trouvé que cette approche fonctionne dans tous les cas:

UILabel* dummyLabel = [UILabel new];
[dummyLabel setFrame:CGRectMake(0, 0, desiredWidth, CGFLOAT_MAX)];
dummyLabel.numberOfLines = 0;
[dummyLabel setLineBreakMode:NSLineBreakByWordWrapping];
dummyLabel.attributedText = myString;
[dummyLabel sizeToFit];
CGSize requiredSize = dummyLabel.frame.size;

Dans mon cas, je devrais faire le calcul sur le fil d'arrière-plan, et l'utilisation d'éléments d'interface utilisateur n'est pas autorisée
Lubbo

4

J'ai eu le même problème de ne pas obtenir une taille précise en utilisant ces techniques et j'ai changé mon approche pour que cela fonctionne.

J'ai une longue chaîne attribuée que j'ai essayé d'intégrer dans une vue de défilement afin qu'elle s'affiche correctement sans être tronquée. Ce que j'ai fait pour que le texte fonctionne de manière fiable, c'est de ne pas définir du tout la hauteur comme contrainte et de laisser la taille intrinsèque prendre le dessus. Maintenant, le texte s'affiche correctement sans être tronqué et je n'ai pas à calculer la hauteur.

Je suppose que si j'avais besoin d'obtenir la hauteur de manière fiable, je créerais une vue cachée et ces contraintes et obtiendrais la hauteur du cadre une fois les contraintes appliquées.


2
Pouvez-vous ajouter un exemple de code pour illustrer cela? THX.
Nathan Buggia

3

Je suis un peu en retard dans le jeu - mais j'ai essayé de trouver un moyen qui fonctionne pour trouver la boîte englobante qui s'adaptera autour d'une chaîne attribuée pour faire un anneau de mise au point comme le fait l'édition d'un fichier dans Finder. tout ce que j'avais essayé a échoué lorsqu'il y a des espaces à la fin de la chaîne ou plusieurs espaces à l'intérieur de la chaîne. boundingRectWithSizeéchoue lamentablement pour cela ainsi que CTFramesetterCreateWithAttributedString.

L'utilisation d'un NSLayoutManagercode suivant semble faire l'affaire dans tous les cas que j'ai trouvés jusqu'à présent et renvoie un rect qui délimite parfaitement la chaîne. Bonus: si vous sélectionnez le texte les bords de la sélection remontent jusqu'aux limites du rect renvoyé. Le code ci-dessous utilise le layoutManager d'un fichier NSTextView.

NSLayoutManager* layout = [self layoutManager];
NSTextContainer* container = [self textContainer];

CGRect focusRingFrame = [layout boundingRectForGlyphRange:NSMakeRange(0, [[self textStorage] length]) inTextContainer:container];

2
textView.textContainerInset = UIEdgeInsetsZero;
NSString *string = @"Some string";
NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:12.0f], NSForegroundColorAttributeName:[UIColor blackColor]};
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
[textView setAttributedText:attributedString];
CGRect textViewFrame = [textView.attributedText boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.view.frame)-8.0f, 9999.0f) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
NSLog(@"%f", ceilf(textViewFrame.size.height));

Fonctionne parfaitement sur toutes les polices!


1

J'ai eu le même problème, mais j'ai reconnu que la hauteur contrainte a été réglée correctement. J'ai donc fait ce qui suit:

-(CGSize)MaxHeighForTextInRow:(NSString *)RowText width:(float)UITextviewWidth {

    CGSize constrainedSize = CGSizeMake(UITextviewWidth, CGFLOAT_MAX);

    NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                          [UIFont fontWithName:@"HelveticaNeue" size:11.0], NSFontAttributeName,
                                          nil];

    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:RowText attributes:attributesDictionary];

    CGRect requiredHeight = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];

    if (requiredHeight.size.width > UITextviewWidth) {
        requiredHeight = CGRectMake(0, 0, UITextviewWidth, requiredHeight.size.height);
    }

    return requiredHeight.size;
}

1
    NSDictionary *stringAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                      [UIFont systemFontOfSize:18], NSFontAttributeName,
                                      [UIColor blackColor], NSForegroundColorAttributeName,
                                      nil];

    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:myLabel.text attributes:stringAttributes];
    myLabel.attributedText = attributedString; //this is the key!

    CGSize maximumLabelSize = CGSizeMake (screenRect.size.width - 40, CGFLOAT_MAX);

    CGRect newRect = [myLabel.text boundingRectWithSize:maximumLabelSize
                                                       options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                    attributes:stringAttributes context:nil];

    self.myLabelHeightConstraint.constant = ceilf(newRect.size.height);

J'ai tout essayé sur cette page et j'avais encore un cas pour un UILabel qui ne se formait pas correctement. En fait, la définition du texte attribué sur l'étiquette a finalement résolu le problème.


1

Une chose que je remarquais, c'est que le rect qui reviendrait (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)contextaurait une largeur plus grande que ce que j'ai passé. Quand cela se produisait, ma chaîne serait tronquée. Je l'ai résolu comme ceci:

NSString *aLongString = ...
NSInteger width = //some width;            
UIFont *font = //your font;
CGRect rect = [aLongString boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
                                        options:(NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin)
                                     attributes:@{ NSFontAttributeName : font,
                                                   NSForegroundColorAttributeName : [UIColor whiteColor]}
                                        context:nil];

if(rect.size.width > width)
{
    return rect.size.height + font.lineHeight;
}
return rect.size.height;

Pour un peu plus de contexte; J'avais du texte sur plusieurs lignes et j'essayais de trouver la bonne hauteur pour l'afficher. BoundRectWithSize renvoyait parfois une largeur plus grande que ce que je spécifierais, donc quand j'utilisais mon passé en largeur et la hauteur calculée pour afficher mon texte, il tronquerait. À partir du test lorsque boundingRectWithSize a utilisé une largeur incorrecte, la valeur de réduction de la hauteur était de 1 ligne. Je vérifierais donc si la largeur était plus grande et si tel était le cas, ajouterais lineHeight de la police pour fournir suffisamment d'espace pour éviter la troncature.


Comment la hauteur de ligne affecte-t-elle la largeur?
Roi Mulia

La hauteur de la ligne @RoiMulia n'affecte pas la largeur. J'ai mis à jour ma réponse pour fournir plus de contexte sur la façon dont cela a corrigé mon bogue.
odyth

0
    NSAttributedString *attributedText =[[[NSAttributedString alloc]
                                          initWithString:joyMeComment.content
                                          attributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:TextFont]}] autorelease];

    CGRect paragraphRect =
    [attributedText boundingRectWithSize:CGSizeMake(kWith, CGFLOAT_MAX)
                                 options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                 context:nil];
    contentSize = paragraphRect.size;

    contentSize.size.height+=10;
    label.frame=contentSize;

si le cadre de l'étiquette n'ajoute pas 10, cette méthode ne fonctionnera jamais! j'espère que cela peut vous aider! bonne chance.


0
Add Following methods in ur code for getting correct size of attribute string 
1.
    - (CGFloat)findHeightForText:(NSAttributedString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font
 {
    UITextView *textView = [[UITextView alloc] init];
    [textView setAttributedText:text];
    [textView setFont:font];
    CGSize size = [textView sizeThatFits:CGSizeMake(widthValue, FLT_MAX)];
    return size.height;

}

2. Call on heightForRowAtIndexPath method
     int h = [self findHeightForText:attrString havingWidth:yourScreenWidth andFont:urFont];

Dans mon cas, je devrais faire le calcul sur le fil d'arrière-plan, et l'utilisation d'éléments d'interface utilisateur n'est pas autorisée
Lubbo

0

Je voudrais ajouter mes réflexions car j'avais exactement le même problème.

J'utilisais UITextViewcar il avait un meilleur alignement du texte (justifiez, qui à l'époque n'était pas disponible dans UILabel), mais afin de "simuler" non-interactif-non-défilable UILabel, je désactivais complètement le défilement, le rebond et l'interaction de l'utilisateur .

Bien sûr, le problème était que le texte était dynamique et, bien que la largeur soit fixe, la hauteur devrait être recalculée chaque fois que je définissais une nouvelle valeur de texte.

boundingRectWithSizeCela ne fonctionnait pas du tout pour moi, d'après ce que je pouvais voir, UITextViewajoutait une marge sur le dessus qui boundingRectWithSizen'entrerait pas dans un décompte, par conséquent, la hauteur récupérée boundingRectWithSizeétait plus petite qu'elle ne devrait l'être.

Étant donné que le texte ne devait pas être mis à jour rapidement, il est simplement utilisé pour certaines informations qui peuvent être mises à jour toutes les 2-3 secondes au maximum, j'ai décidé de l'approche suivante:

/* This f is nested in a custom UIView-inherited class that is built using xib file */
-(void) setTextAndAutoSize:(NSString*)text inTextView:(UITextView*)tv
{
    CGFloat msgWidth = tv.frame.size.width; // get target's width

    // Make "test" UITextView to calculate correct size
    UITextView *temp = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, msgWidth, 300)]; // we set some height, really doesn't matter, just put some value like this one.
    // Set all font and text related parameters to be exact as the ones in targeted text view
    [temp setFont:tv.font];
    [temp setTextAlignment:tv.textAlignment];
    [temp setTextColor:tv.textColor];
    [temp setText:text];

    // Ask for size that fits :P
    CGSize tv_size = [temp sizeThatFits:CGSizeMake(msgWidth, 300)];

    // kill this "test" UITextView, it's purpose is over
    [temp release];
    temp = nil;

    // apply calculated size. if calcualted width differs, I choose to ignore it anyway and use only height because I want to have width absolutely fixed to designed value
    tv.frame = CGRectMake(tv.frame.origin.x, tv.frame.origin.y, msgWidth, tv_size.height );
}

* Le code ci-dessus n'est pas directement copié à partir de ma source, j'ai dû l'ajuster / le supprimer d'un tas d'autres éléments non nécessaires pour cet article. Ne le prenez pas pour copier-coller-et-cela-fonctionnera-code.

L'inconvénient évident est qu'il a l'allocation et la libération, pour chaque appel.

Mais l'avantage est que vous évitez de dépendre de la compatibilité entre la façon dont boundingRectWithSize dessine le texte et calcule sa taille et l'implémentation du dessin de texte dans UITextView(ou UILabelque vous pouvez également utiliser simplement remplacer UITextViewpar UILabel). Tous les «bogues» qu'Apple pourrait avoir sont ainsi évités.

PS Il semblerait que vous ne devriez pas avoir besoin de ce "temp" UITextViewet que vous pouvez simplement demander sizeThatFitsdirectement à la cible, mais cela n'a pas fonctionné pour moi. Bien que la logique dirait que cela devrait fonctionner et que l'allocation / la libération de temporaires UITextViewne sont pas nécessaires, ce n'est pas le cas. Mais cette solution fonctionnait parfaitement pour tout texte que je mettrais en place.


Dans mon cas, je devrais faire le calcul sur le fil d'arrière-plan et l'utilisation des éléments de l'interface utilisateur n'est pas autorisée
Lubbo

0

Ok donc j'ai passé beaucoup de temps à déboguer ça. J'ai découvert que la hauteur maximale du texte telle que définie par boundingRectWithSizeautorisé à afficher le texte par my UITextViewétait inférieure à la taille du cadre.

Dans mon cas, le cadre est au plus 140pt mais l'UITextView tolère les textes au maximum 131pt.

J'ai dû comprendre cela manuellement et coder en dur la hauteur maximale «réelle».

Voici ma solution:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    NSString *proposedText = [textView.text stringByReplacingCharactersInRange:range withString:text];
    NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:proposedText];
    CGRect boundingRect;
    CGFloat maxFontSize = 100;
    CGFloat minFontSize = 30;
    CGFloat fontSize = maxFontSize + 1;
    BOOL fit;
    NSLog(@"Trying text: \"%@\"", proposedText);
    do {
        fontSize -= 1;
        //XXX Seems like trailing whitespaces count for 0. find a workaround
        [attributedText addAttribute:NSFontAttributeName value:[textView.font fontWithSize:fontSize] range:NSMakeRange(0, attributedText.length)];
        CGFloat padding = textView.textContainer.lineFragmentPadding;
        CGSize boundingSize = CGSizeMake(textView.frame.size.width - padding * 2, CGFLOAT_MAX);
        boundingRect = [attributedText boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil];
        NSLog(@"bounding rect for font %f is %@; (max is %f %f). Padding: %f", fontSize, NSStringFromCGRect(boundingRect), textView.frame.size.width, 148.0, padding);
        fit =  boundingRect.size.height <= 131;
    } while (!fit && fontSize > minFontSize);
    if (fit) {
        self.textView.font = [self.textView.font fontWithSize:fontSize];
        NSLog(@"Fit!");
    } else {
        NSLog(@"No fit");
    }
    return fit;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.