web-dev-qa-db-fra.com

Comment obtenir de la hauteur pour NSAttributedString à une largeur fixe

Je souhaite dessiner NSAttributedStrings dans des boîtes de largeur fixe, mais je ne parviens pas à calculer la hauteur correcte à prendre lors du dessin. Jusqu'à présent, j'ai essayé:

  1. Appeler - (NSSize) size, mais les résultats sont inutiles (dans ce but), car ils donneront la largeur souhaitée par la chaîne.

  2. Appelez - (void)drawWithRect:(NSRect)rect options:(NSStringDrawingOptions)options avec un rectangle dont la largeur correspond à celle que je souhaite et NSStringDrawingUsesLineFragmentOrigin dans les options, exactement comme dans mon dessin. Les résultats sont ... difficiles à comprendre; certainement pas ce que je cherche. (Comme indiqué dans plusieurs endroits, y compris this Cocoa-Dev thread).

  3. Créer un NSTextView temporaire et faire:
    [[tmpView textStorage] setAttributedString:aString];
    [tmpView setHorizontallyResizable:NO];
    [tmpView sizeToFit];

    Lorsque j'interroge le cadre de tmpView, la largeur est toujours celle souhaitée et la hauteur est souvent correcte ... jusqu'à obtenir des chaînes plus longues, qui correspondent souvent à la moitié de la taille requise. (Il ne semble pas y avoir de taille maximale atteinte: une image aura une hauteur de 273,0 (environ 300 trop courte), l’autre sera de 478,0 (seulement 60 trop trop courte)).

J'apprécierais tous les conseils, si quelqu'un d'autre a réussi cela.

46
bonaldi
-[NSAttributedString boundingRectWithSize:options:]

Vous pouvez spécifier NSStringDrawingUsesDeviceMetrics pour obtenir l'union de toutes les limites de glyphes .

Contrairement à -[NSAttributedString size], la NSRect renvoyée représente les dimensions de la zone qui changeraient si la chaîne est dessinée.

Comme @Bryan commente, boundingRectWithSize:options: est obsolète (non recommandé) sous OS X 10.11 et versions ultérieures. En effet, le style des chaînes est désormais dynamique en fonction du contexte.

Pour OS X 10.11 et les versions ultérieures, consultez la documentation de Calculating Text Height de Apple.

31
Graham Miln

Vous pourriez être intéressé par la grande catégorie NS(Attributed)String+Geometrics ((sous OS X uniquement) de Jerry Krinock, conçue pour effectuer toutes sortes de mesures de chaîne, y compris ce que vous recherchez. 

6
Rob Keniger

J'ai une chaîne complexe d'attributs avec plusieurs polices et j'ai obtenu des résultats incorrects avec certaines des réponses ci-dessus que j'ai essayées en premier. L'utilisation d'une UITextView m'a donné la hauteur correcte, mais était trop lente pour mon cas d'utilisation (dimensionnement des cellules de collecte). J'ai écrit le code Swift en utilisant la même approche générale que celle décrite dans le document Apple référencé précédemment et décrit par Erik. Cela m'a donné des résultats corrects avec une exécution plus rapide que d'avoir un UITextView faire le calcul.

private func heightForString(_ str : NSAttributedString, width : CGFloat) -> CGFloat {
    let ts = NSTextStorage(attributedString: str)

    let size = CGSize(width:width, height:CGFloat.greatestFiniteMagnitude)

    let tc = NSTextContainer(size: size)
    tc.lineFragmentPadding = 0.0

    let lm = NSLayoutManager()
    lm.addTextContainer(tc)

    ts.addLayoutManager(lm)
    lm.glyphRange(forBoundingRect: CGRect(Origin: .zero, size: size), in: tc)

    let rect = lm.usedRect(for: tc)

    return rect.integral.size.height
}
5
Matt R

Swift 3:

let attributedStringToMeasure = NSAttributedString(string: textView.text, attributes: [
        NSFontAttributeName: UIFont(name: "GothamPro-Light", size: 15)!,
        NSForegroundColorAttributeName: ClickUpConstants.defaultBlackColor
])

let placeholderTextView = UITextView(frame: CGRect(x: 0, y: 0, width: widthOfActualTextView, height: 10))
placeholderTextView.attributedText = attributedStringToMeasure
let size: CGSize = placeholderTextView.sizeThatFits(CGSize(width: widthOfActualTextView, height: CGFloat.greatestFiniteMagnitude))

height = size.height

Cette réponse fonctionne très bien pour moi, contrairement aux autres qui me donnaient des hauteurs incorrectes pour les grandes chaînes. 

Si vous voulez faire cela avec du texte normal au lieu du texte attribué, procédez comme suit:

let placeholderTextView = UITextView(frame: CGRect(x: 0, y: 0, width: ClickUpConstants.screenWidth - 30.0, height: 10))
placeholderTextView.text = "Some text"
let size: CGSize = placeholderTextView.sizeThatFits(CGSize(width: widthOfActualTextView, height: CGFloat.greatestFiniteMagnitude))

height = size.height
2
Josh O'Connor

Sous OS X 10.11+, la méthode suivante fonctionne pour moi (à partir de Calcul de la hauteur du texte document)

- (CGFloat)heightForString:(NSAttributedString *)myString atWidth:(float)myWidth
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:myString];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:
        NSMakeSize(myWidth, FLT_MAX)];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];
    [layoutManager glyphRangeForTextContainer:textContainer];
    return [layoutManager
        usedRectForTextContainer:textContainer].size.height;
}
2
Erik

Utiliser la méthode NSAttributedString

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context

La taille est la contrainte sur la surface, la largeur de la surface calculée est limitée à la largeur spécifiée alors que la hauteur est flexible en fonction de cette largeur. On peut spécifier nil pour contexte si ce n'est pas disponible. Pour obtenir une taille de texte multiligne, utilisez NSStringDrawingUsesLineFragmentOrigin pour les options.

2
CodeBrew

Je viens de perdre beaucoup de temps à ce sujet, alors je fournis une réponse supplémentaire pour en sauver d'autres à l'avenir. La réponse de Graham est correcte à 90%, mais il lui manque un élément clé:

Pour obtenir des résultats précis avec -boundingRectWithSize:options: vous DEVEZ passer les options suivantes :

NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesDeviceMetrics|NSStringDrawingUsesFontLeading`

Si vous omettez celui de lineFragmentOrigin, vous obtiendrez un non-sens; le rect retourné aura une hauteur d'une seule ligne et ne respectera pas du tout la taille passée dans la méthode.

Pourquoi ceci est si compliqué et si mal documenté me dépasse. Mais là vous l'avez. Passez ces options et cela fonctionnera parfaitement (sous OS X au moins).

1
Bryan

Aucune réponse sur cette page n'a fonctionné pour moi, pas plus que l'ancien code Objective-C de la documentation d'Apple . Ce que j'ai finalement réussi à faire pour un UITextView est tout d'abord de définir sa propriété text ou attributedText puis de calculer la taille requise comme ceci:

let size = textView.sizeThatFits(CGSize(width: maxWidth, height: CGFloat.max))

Marche parfaitement. Booyah!

0
Patrick Lynch

Comme beaucoup de gars mentionnés ci-dessus, et base sur mon test.

J'utilise open func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], context: NSStringDrawingContext?) -> CGRect sur iOS comme ceci:

let rect = attributedTitle.boundingRect(with: CGSize(width:200, height:0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)

Ici le200 est la largeur fixe comme vous le souhaitez, height Je lui donne 0 car je pense qu'il est préférable de dire que la hauteur de l'API est unlimited .

L'option n'est pas si importante ici , J'ai essayé .usesLineFragmentOrigin ou .usesLineFragmentOrigin.union(.usesFontLeading) ou .usesLineFragmentOrigin.union(.usesFontLeading).union(.usesDeviceMetrics), cela donne le même résultat.

Et le résultat est attendu comme si c'était mon cas. 

Merci. 

0
JerryZhou

J'ai trouvé la classe d'assistance pour trouver la hauteur et la largeur de attributText (code testé)

https://Gist.github.com/azimin/aa1a79aefa1cec031152fa63401d2292

Ajouter le fichier ci-dessus dans votre projet

Comment utiliser

let attribString = AZTextFrameAttributes(attributedString: lbl.attributedText!)
let width : CGFloat = attribString.calculatedTextWidth()
print("width is :: >> \(width)")
0
Hardik Thakkar