web-dev-qa-db-fra.com

Calculer la taille de la police pour l'ajuster au cadre - Texte de base - NSAttributedString - iOS

J'ai du texte que je dessine dans un cadre fixe via un NSAttributedString (code ci-dessous). Pour le moment, je suis en train de coder la taille du texte à 16. Ma question est la suivante: existe-t-il un moyen de calculer la taille la mieux adaptée au texte pour le cadre donné?

- (void)drawText:(CGContextRef)contextP startX:(float)x startY:(float)
y withText:(NSString *)standString
{
    CGContextTranslateCTM(contextP, 0, (bottom-top)*2);
    CGContextScaleCTM(contextP, 1.0, -1.0);

    CGRect frameText = CGRectMake(1, 0, (right-left)*2, (bottom-top)*2);

    NSMutableAttributedString * attrString = [[NSMutableAttributedString alloc] initWithString:standString];
    [attrString addAttribute:NSFontAttributeName
                      value:[UIFont fontWithName:@"Helvetica-Bold" size:16.0]
                      range:NSMakeRange(0, attrString.length)];

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrString));
    struct CGPath * p = CGPathCreateMutable();
    CGPathAddRect(p, NULL, frameText);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), p, NULL);

    CTFrameDraw(frame, contextP);
}
20
GuybrushThreepwood

La seule façon pour moi de voir cela est possible est d’avoir un système qui exécute le calcul de la taille, puis ajuste la taille et se répète jusqu’à ce qu’il trouve la bonne taille.

C'est à dire. mettre en place un algorithme de bissection qui va entre certaines tailles.

c.-à-d. utilisez-le pour la taille 10 . Trop petit .. Taille 20 . Trop petit .. Taille 30 . Trop grand .. Taille 25 . Trop petit . Taille 27 . Parfait, utilisez la taille 27.

Vous pouvez même commencer par centaines.

Taille 100 . Trop grand . Taille 50 . Etc ...

9
Fogmeister

Voici un simple morceau de code qui déterminera la taille de police maximum pour s’inscrire dans les limites d’un cadre:

UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.text = @"Some text";
float largestFontSize = 12;
while ([label.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:largestFontSize]}].width > modifierFrame.size.width)
{
     largestFontSize--;
}
label.font = [UIFont systemFontOfSize:largestFontSize];
22
user3072402

La réponse actuellement acceptée parle d'un algorithme, mais iOS fournit des calculs pour un objet NSString. J'utiliserais sizeWithAttributes: de la classe NSString.

sizeWithAttributes:

Renvoie la taille de la boîte englobante occupée par le destinataire lorsqu'il est tracé avec les attributs donnés.

    - (CGSize)sizeWithAttributes:(NSDictionary *)attributes

Source: Apple Docs - Référence des compléments UIKit NSString

EDIT A mal interprété la question, cette réponse est donc fausse.

6
CloakedEddy

Une petite astuce permet d’utiliser sizeWithAttributes: sans avoir à itérer pour obtenir le bon résultat:

NSSize sampleSize = [wordString sizeWithAttributes:
    @{ NSFontAttributeName: [NSFont fontWithName:fontName size:fontSize] }];
CGFloat ratio = rect.size.width / sampleSize.width;
fontSize *= ratio;

Assurez-vous que la variable fontSize de l'échantillon est suffisamment grande pour obtenir de bons résultats.

4
Holtwick

Vous pouvez utiliser sizeWithFont:

[myString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:24]   
constrainedToSize:CGSizeMake(293, 10000)] // put the size of your frame

Mais il est obsolète dans iOS 7, je recommande donc de travailler avec des chaînes dans UILabel:

[string sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0f]}];

Si vous travaillez avec un rect:

CGRect textRect = [text boundingRectWithSize:mySize
                                 options:NSStringDrawingUsesLineFragmentOrigin
                              attributes:@{NSFontAttributeName:FONT}
                                 context:nil];

CGSize size = textRect.size;
4
Pull

Voici le code qui fera exactement cela: calculer la taille de police optimale dans certaines limites. Cet exemple est dans le contexte de la sous-classe UITextView et utilise donc ses limites comme "image donnée":

func binarySearchOptimalFontSize(min: Int, max: Int) -> Int {
    let middleSize = (min + max) / 2

    if min > max {
        return middleSize
    }

    let middleFont = UIFont(name: font!.fontName, size: CGFloat(middleSize))!

    let attributes = [NSFontAttributeName : middleFont]
    let attributedString = NSAttributedString(string: text, attributes: attributes)

    let size = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
    let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
    let textSize = attributedString.boundingRect(with: size, options: options, context: nil)

    if textSize.size.equalTo(bounds.size) {
        return middleSize
    } else if (textSize.height > bounds.size.height || textSize.width > bounds.size.width) {
        return binarySearchOptimalFontSize(min: min, max: middleSize - 1)
    } else {
        return binarySearchOptimalFontSize(min: middleSize + 1, max: max)
    }
}

J'espère que ça aide.

3
tadija

Vous pouvez définir la propriété UILabel de adjustsFontSizeToFitWidth sur YES conformément à documentation d'Apple

2
MuhammadBassio

Encore plus facile/rapide (mais bien sûr approximatif), voici ce qui suit:

class func calculateOptimalFontSize(textLength:CGFloat, boundingBox:CGRect) -> CGFloat
    {
        let area:CGFloat = boundingBox.width * boundingBox.height
        return sqrt(area / textLength)
    }

Nous supposons que chaque caractère est N x N pixels, nous calculons donc simplement le nombre de fois où N x N passe dans le cadre de sélection.

2
sabiland

C'est le code pour que la taille de police dynamique change en fonction de la largeur du cadre, en utilisant la logique des autres réponses. La boucle while peut être dangereuse, n'hésitez donc pas à soumettre des améliorations.

float fontSize = 17.0f; //initial font size
CGSize rect;
while (1) {
   fontSize = fontSize+0.1;
   rect = [watermarkText sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}];
    if ((int)rect.width == (int)subtitle1Text.frame.size.width) {
        break;
    }
}
subtitle1Text.fontSize = fontSize;
1
vatti

J'aime l'approche proposée par @holtwick, mais j'ai constaté qu'elle surestimait parfois ce qui conviendrait. J'ai créé un Tweak qui semble bien fonctionner lors de mes tests. Astuce: N'oubliez pas de tester avec des lettres très larges comme "WWW" ou même ""

func idealFontSize(for text: String, font: UIFont, width: CGFloat) -> CGFloat {
    let baseFontSize = CGFloat(256)
    let textSize = text.size(attributes: [NSFontAttributeName: font.withSize(baseFontSize)])
    let ratio = width / textSize.width

    let ballparkSize = baseFontSize * ratio
    let stoppingSize = ballparkSize / CGFloat(2) // We don't want to loop forever, if we've already come down to 50% of the ballpark size give up
    var idealSize = ballparkSize
    while (idealSize > stoppingSize && text.size(attributes: [NSFontAttributeName: font.withSize(idealSize)]).width > width) {
        // We subtract 0.5 because sometimes ballparkSize is an overestimate of a size that will fit
        idealSize -= 0.5
    }

    return idealSize
}
0
C. Clark

Voici une méthode qui semble bien fonctionner pour iOS 9 utilisant des objets UITextView. Vous pourriez avoir à le tweet un peu pour d'autres applications.

/*!
 * Find the height of the smallest rectangle that will enclose a string using the given font.
 *
 * @param string            The string to check.
 * @param font              The drawing font.
 * @param width             The width of the drawing area.
 *
 * @return The height of the rectngle enclosing the text.
 */

- (float) heightForText: (NSString *) string font: (UIFont *) font width: (float) width {
    NSDictionary *fontAttributes = [NSDictionary dictionaryWithObject: font
                                                               forKey: NSFontAttributeName];
    CGRect rect = [string boundingRectWithSize: CGSizeMake(width, INT_MAX)
                                       options: NSStringDrawingUsesLineFragmentOrigin
                                    attributes: fontAttributes
                                       context: nil];
    return rect.size.height;
}

/*!
 * Find the largest font size that will allow a block of text to fit in a rectangle of the given size using the system
 * font.
 *
 * The code is tested and optimized for UITextView objects.
 *
 * The font size is determined to ±0.5. Change delta in the code to get more or less precise results.
 *
 * @param string            The string to check.
 * @param size              The size of the bounding rectangle.
 *
 * @return: The font size.
 */

- (float) maximumSystemFontSize: (NSString *) string size: (CGSize) size {
    // Hack: For UITextView, the last line is clipped. Make sure it's not one we care about.
    if ([string characterAtIndex: string.length - 1] != '\n') {
        string = [string stringByAppendingString: @"\n"];
    }
    string = [string stringByAppendingString: @"M\n"];

    float maxFontSize = 16.0;
    float maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
    while (maxHeight < size.height) {
        maxFontSize *= 2.0;
        maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
    }

    float minFontSize = maxFontSize/2.0;
    float minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
    while (minHeight > size.height) {
        maxFontSize = minFontSize;
        minFontSize /= 2.0;
        maxHeight = minHeight;
        minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
    }

    const float delta = 0.5;
    while (maxFontSize - minFontSize > delta) {
        float middleFontSize = (minFontSize + maxFontSize)/2.0;
        float middleHeight = [self heightForText: string font: [UIFont systemFontOfSize: middleFontSize] width: size.width];
        if (middleHeight < size.height) {
            minFontSize = middleFontSize;
            minHeight = middleHeight;
        } else {
            maxFontSize = middleFontSize;
            maxHeight = middleHeight;
        }
    }

    return minFontSize;
}
0
Mike

Voici ma solution dans Swift 4:

private func adjustedFontSizeOf(label: UILabel) -> CGFloat {
    guard let textSize = label.text?.size(withAttributes: [.font: label.font]), textSize.width > label.bounds.width else {
        return label.font.pointSize
    }

    let scale = label.bounds.width / textSize.width
    let actualFontSize = scale * label.font.pointSize

    return actualFontSize
}

J'espère que ça aide quelqu'un.

0
lam.nguyen2169