font Рассчитать размер шрифта, чтобы соответствовать рамке-основной текст-NSAttributedString-iOS




uilabel size (10)

У меня есть текст, который я рисую в фиксированную рамку через 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);
}

Вот простой фрагмент кода, который определит максимальный размер шрифта, чтобы поместиться в пределах рамки:

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

Мне нравится подход @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
}

Небольшая хитрость помогает использовать sizeWithAttributes: без необходимости итерации для получения правильного результата:

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

Убедитесь, что fontSize для образца достаточно велик, чтобы получить хорошие результаты.


Вы можете использовать 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]}];

Если вы работаете с прямоугольником:

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

CGSize size = textRect.size;

Вот код, который будет делать именно это: вычислить оптимальный размер шрифта в определенных пределах. Этот пример находится в контексте подкласса 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)
    }
}

Надеюсь, это поможет.


Вы можете установить для свойства adjustsFontSizeToFitWidth значение YES в соответствии с документацией Apple


Единственный способ убедиться, что это возможно, - это иметь систему, которая выполняет вычисление размера, затем корректирует размер и повторяет его, пока не найдет нужный размер.

Т.е. настроить алгоритм деления пополам между определенными размерами.

т.е. запустить его для размера 10. Слишком маленький. Размер 20. Слишком маленький. Размер 30. Слишком большой. Размер 25. Слишком маленький. Размер 27. Точно, используйте размер 27.

Вы могли бы даже начать сотни.

Размер 100. Слишком большой. Размер 50. и т. Д ...


Это код для динамического изменения размера шрифта в зависимости от ширины кадра, используя логику из других ответов. Цикл 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;

Вот метод, который, кажется, хорошо работает для iOS 9 с использованием объектов UITextView . Возможно, вам придется немного чирикать это для других приложений.

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

Еще более простой / быстрый (но, конечно, приблизительный) способ был бы таким:

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

Мы предполагаем, что каждый символ имеет размер N x N пикселей, поэтому мы просто рассчитываем, сколько раз N x N входит в ограничивающую рамку.





nsattributedstring