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

时间:2013-12-09 10:47:42

标签: ios objective-c fonts core-text nsattributedstring

我有一些文字,我通过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);
}

13 个答案:

答案 0 :(得分:23)

这是一段简单的代码,可以找出最适合框架范围的字体大小:

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

答案 1 :(得分:9)

我能看到这种可能性的唯一方法是让系统运行大小计算,然后调整大小并重复,直到找到合适的大小。

即。设置一个在某些大小之间的二等分算法。

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

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

尺寸100。 太大。 大小50。 等...

答案 2 :(得分:6)

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

  

<强> sizeWithAttributes:

     

返回使用给定属性绘制时接收器占用的边界框大小。

    - (CGSize)sizeWithAttributes:(NSDictionary *)attributes

来源:Apple Docs - NSString UIKit Additions Reference

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

答案 3 :(得分:4)

您可以使用sizeWithFont:

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

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

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

如果您正在使用rect:

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

CGSize size = textRect.size;

答案 4 :(得分:3)

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

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

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

答案 5 :(得分:3)

以下是完全相同的代码:在某些范围内计算最佳字体大小。此示例位于UITextView子类的上下文中,因此它使用其边界作为&#34;给定框架&#34;:

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

我希望有所帮助。

答案 6 :(得分:2)

您可以按Apple's documentation

UILabel的属性adjustsFontSizeToFitWidth设置为YES

答案 7 :(得分:2)

更简单/更快(但当然是近似的)方式将是:

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

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

答案 8 :(得分:1)

这是使用其他答案中的逻辑使动态字体大小按帧宽度更改的代码。 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 :(得分:1)

Apple 没有提供任何方法来找出适合给定矩形中文本的字体大小。想法是基于 BinarySearch 找出完美适合给定大小的最佳字体大小。以下扩展尝试不同的字体大小以收敛到完美的字体大小值。

import UIKit

extension UITextView {
    @discardableResult func adjustFontToFit(_ rect: CGSize, minFontSize: CGFloat = 5, maxFontSize: CGFloat = 100, accuracy: CGFloat = 0.1) -> CGFloat {
        // To avoid text overflow
        let targetSize = CGSize(width: floor(rect.width), height: rect.height)

        var minFontSize = minFontSize
        var maxFontSize = maxFontSize
        var fittingSize = targetSize

        while maxFontSize - minFontSize > accuracy {
            let midFontSize = (minFontSize + maxFontSize) / 2
            font = font?.withSize(midFontSize)
            fittingSize = sizeThatFits(targetSize)

            if fittingSize.height <= rect.height {
                minFontSize = midFontSize
            } else {
                maxFontSize = midFontSize
            }
        }

        // It might be possible that while loop break with last assignment
        // to `maxFontSize`, which can overflow the available height
        // Using `minFontSize` will be failsafe 
        font = font?.withSize(minFontSize)
        return minFontSize
    }
}

答案 10 :(得分:0)

这是一种使用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)

我喜欢@holtwick给出的方法,但发现它有时会高估适合的方法。我创建了一个调整,似乎在我的测试中运行良好。提示:不要忘记测试像#34; WWW&#34;甚至&#34;௵௵௵&#34;

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)

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

我希望它有所帮助。