Вычислить размер шрифта для рамки рамки - основной текст - NSAttributedString - iOS

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

Ответы

Ответ 1

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

т.е. установите алгоритм деления пополам, который идет между определенными размерами.

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

Вы даже можете начать сотнями.

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

Ответ 2

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

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

В настоящее время ответы на переговоры по алгоритму, но iOS предоставляет вычисления для объекта NSString. Я бы использовал sizeWithAttributes: класса NSString.

sizeWithAttributes:

Возвращает размер ограничивающей рамки, который занимает приемник при рисовании с заданными атрибутами.

    - (CGSize)sizeWithAttributes:(NSDictionary *)attributes

Источник: Apple Docs - ссылка NCCtring UIKit дополнения

РЕДАКТИРОВАТЬ Непоследовательность вопроса, поэтому этот ответ не соответствует значению.

Ответ 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;

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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 входит в ограничительную рамку.

Ответ 9

Здесь метод, который, кажется, хорошо работает для 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 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;
}

Ответ 10

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

Я надеюсь, что это поможет.

Ответ 11

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