Calculate Font Size to Fit Frame - Core Text - NSAttributedString - iOS

假如想象 提交于 2020-01-10 08:48:08

问题


I have some text which I am drawing into a fixed frame via an NSAttributedString (code below). At the moment I am hard coding the text size to 16. My question is, is there a way to calculate the best fit size for the text for the given frame ?

- (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);
}

回答1:


The only way I can see this being possible is to have a system that runs the size calculation then adjusts the size and repeats until it finds the right size.

I.e. set up a bisecting algorithm that goes between certain sizes.

i.e. run it for size 10. Too small. Size 20. Too small. Size 30. Too big. Size 25. Too small. Size 27. Just right, use size 27.

You could even start in hundreds.

Size 100. Too big. Size 50. etc...




回答2:


Here is a simple piece of code that will figure out the maximum font size to fit within the bounds of a frame:

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];



回答3:


The currently accepted answer talks of an algorithm, but iOS provides calculations for an NSString object. I would use sizeWithAttributes: of the NSString class.

sizeWithAttributes:

Returns the bounding box size the receiver occupies when drawn with the given attributes.

    - (CGSize)sizeWithAttributes:(NSDictionary *)attributes

Source: Apple Docs - NSString UIKit Additions Reference

EDIT Misinterpreted the question, so this answer is off the mark.




回答4:


You could use sizeWithFont :

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

But it is deprecated in iOS 7, so I recommend if working with string in UILabel :

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

If you are working with a rect :

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

CGSize size = textRect.size;



回答5:


You can set the UILabel's property adjustsFontSizeToFitWidth to YES as per Apple's documentation




回答6:


Even more easy/faster (but of course approximate) way would be this:

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

We are assuming each char is N x N pixels, so we just calculate how many times N x N goes inside bounding box.




回答7:


A little trick helps to make use of sizeWithAttributes: without the need of iterating for the right result:

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

Make sure the fontSize for the sample is big enough to get good results.




回答8:


Here is code which will do exactly that: calculate optimal font size within some bounds. This sample is in context of UITextView subclass, so it's using its bounds as a "given frame":

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)
    }
}

I hope that helps.




回答9:


This is the code to have dynamic font size changing by the frame width, using the logic from the other answers. The while loop might be dangerous, so please donot hesitate to submit improvements.

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;



回答10:


Here's a method that seems to work well for iOS 9 using UITextView objects. You might have to tweet it a bit for other 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;
}



回答11:


I like the approach given by @holtwick, but found that it would sometimes overestimate what would fit. I created a tweak that seems to work well in my tests. Tip: Don't forget to test with really wide letters like "WWW" or even "௵௵௵"

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
}



回答12:


Here is my solution in 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
}

I hope it helps someone.



来源:https://stackoverflow.com/questions/20468641/calculate-font-size-to-fit-frame-core-text-nsattributedstring-ios

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!