计算字体大小以适合框架 - 核心文本 - NSAttributedString - iOS

时间:2023-02-01 07:56:22

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 ?

我有一些文本,我通过NSAttributedString(下面的代码)绘制到一个固定的框架。目前我正在努力将文本大小编码为16.我的问题是,有没有办法计算给定帧文本的最佳拟合大小?

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

12 个解决方案

#1


9  

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.

即运行它的大小10.太小。尺寸20.太小。大小30.太大了。大小25.太小了。大小27.恰到好处,使用27号。

You could even start in hundreds.

你甚至可以从数百人开始。

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

大小100.太大了。大小50.等...

#2


21  

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


6  

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

目前接受的答案是谈论算法,但iOS提供了NSString对象的计算。我会使用NSString类的sizeWithAttributes:

sizeWithAttributes:

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

来源:Apple Docs - NSString UIKit Additions Reference

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

编辑错误解释了这个问题,所以这个答案是不合适的。

#4


4  

You could use sizeWithFont :

你可以使用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 :

但它在iOS 7中已被弃用,因此我建议在UILabel中使用字符串:

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

If you are working with a rect :

如果您正在使用rect:

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

CGSize size = textRect.size;

#5


2  

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

您可以根据Apple的文档将UILabel的属性adjustsFontSizeToFitWidth设置为YES

#6


2  

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

一个小技巧有助于使用sizeWithAttributes:无需迭代以获得正确的结果:

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.

确保样本的fontSize足够大以获得良好的结果。

#7


2  

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":

下面是完全相同的代码:在某些范围内计算最佳字体大小。此示例位于UITextView子类的上下文中,因此它将其边界用作“给定帧”:

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.

我希望有所帮助。

#8


1  

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.

这是使用其他答案中的逻辑使动态字体大小按帧宽度更改的代码。 while循环可能很危险,所以请不要犹豫提交改进。

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;

#9


0  

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.

我们假设每个char都是N x N像素,所以我们只计算N x N进入边界框的次数。

#10


0  

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.

这是一个使用UITextView对象似乎适用于iOS 9的方法。您可能需要为其他应用程序稍微发推特。

/*!
 * 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


0  

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 "௵௵௵"

我喜欢@holtwick给出的方法,但发现它有时会高估适合的方法。我创建了一个调整,似乎在我的测试中运行良好。提示:不要忘记使用像“WWW”甚至“௵௵௵”这样的宽字母进行测试

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


0  

Here is my solution in swift 4:

这是我在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.

我希望它对某人有帮助。

#1


9  

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.

即运行它的大小10.太小。尺寸20.太小。大小30.太大了。大小25.太小了。大小27.恰到好处,使用27号。

You could even start in hundreds.

你甚至可以从数百人开始。

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

大小100.太大了。大小50.等...

#2


21  

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


6  

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

目前接受的答案是谈论算法,但iOS提供了NSString对象的计算。我会使用NSString类的sizeWithAttributes:

sizeWithAttributes:

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

来源:Apple Docs - NSString UIKit Additions Reference

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

编辑错误解释了这个问题,所以这个答案是不合适的。

#4


4  

You could use sizeWithFont :

你可以使用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 :

但它在iOS 7中已被弃用,因此我建议在UILabel中使用字符串:

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

If you are working with a rect :

如果您正在使用rect:

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

CGSize size = textRect.size;

#5


2  

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

您可以根据Apple的文档将UILabel的属性adjustsFontSizeToFitWidth设置为YES

#6


2  

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

一个小技巧有助于使用sizeWithAttributes:无需迭代以获得正确的结果:

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.

确保样本的fontSize足够大以获得良好的结果。

#7


2  

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":

下面是完全相同的代码:在某些范围内计算最佳字体大小。此示例位于UITextView子类的上下文中,因此它将其边界用作“给定帧”:

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.

我希望有所帮助。

#8


1  

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.

这是使用其他答案中的逻辑使动态字体大小按帧宽度更改的代码。 while循环可能很危险,所以请不要犹豫提交改进。

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;

#9


0  

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.

我们假设每个char都是N x N像素,所以我们只计算N x N进入边界框的次数。

#10


0  

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.

这是一个使用UITextView对象似乎适用于iOS 9的方法。您可能需要为其他应用程序稍微发推特。

/*!
 * 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


0  

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 "௵௵௵"

我喜欢@holtwick给出的方法,但发现它有时会高估适合的方法。我创建了一个调整,似乎在我的测试中运行良好。提示:不要忘记使用像“WWW”甚至“௵௵௵”这样的宽字母进行测试

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


0  

Here is my solution in swift 4:

这是我在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.

我希望它对某人有帮助。