Как настроить размер шрифта метки, чтобы он соответствовал прямоугольнику?

Да, есть классная myLabel.adjustsFontSizeToFitWidth = YES; собственность. Но как только метка будет состоять из двух или более строк, размер текста не изменится ни на что. Так что он просто усекается с помощью ... если он не помещается в прямоугольник.

Есть ли другой способ сделать это?


person dontWatchMyProfile    schedule 16.05.2010    source источник


Ответы (14)


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

Этот фрагмент начинается с размера 300 pt и пытается разместить метку в целевом прямоугольнике, уменьшив размер шрифта.

- (void) sizeLabel: (UILabel *) label toRect: (CGRect) labelRect {

    // Set the frame of the label to the targeted rectangle
    label.frame = labelRect;

    // Try all font sizes from largest to smallest font size
    int fontSize = 300;
    int minFontSize = 5;

    // Fit label width wize
    CGSize constraintSize = CGSizeMake(label.frame.size.width, MAXFLOAT);

    do {
        // Set current font size
        label.font = [UIFont fontWithName:label.font.fontName size:fontSize];

        // Find label size for current font size
        CGRect textRect = [[label text] boundingRectWithSize:constraintSize
                                                     options:NSStringDrawingUsesLineFragmentOrigin
                                                  attributes:@{NSFontAttributeName: label.font}
                                                     context:nil];

        CGSize labelSize = textRect.size;

        // Done, if created label is within target size
        if( labelSize.height <= label.frame.size.height )
            break;

        // Decrease the font size and try again
        fontSize -= 2;

    } while (fontSize > minFontSize);
}

Я думаю, что вышесказанное объясняет, что происходит. Более быстрая реализация может использовать кэширование и двоичный поиск argarcians следующим образом.

+ (CGFloat) fontSizeForString: (NSString*) s inRect: (CGRect) labelRect  {
    // Cache repeat queries
    static NSMutableDictionary* mutableDict = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutableDict = [NSMutableDictionary dictionary];
    });

    NSString* key = [NSString stringWithFormat:@"%@_%d_%d", s, (int) labelRect.size.width, (int) labelRect.size.height];
    NSNumber* value = [mutableDict objectForKey:key];
    if (value)
        return value.doubleValue;

    // Set the frame of the label to the targeted rectangle
    UILabel* label = [[UILabel alloc] init];
    label.text = s;
    label.frame = labelRect;

    // Hopefully between 5 and 300
    CGFloat theSize = (CGFloat) [self binarySearchForFontSizeForLabel:label withMinFontSize:5 withMaxFontSize:300 withSize:label.frame.size];
    [mutableDict setObject:@(theSize) forKey:key];
    return  theSize;
}


+ (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
    // If the sizes are incorrect, return 0, or error, or an assertion.
    if (maxFontSize < minFontSize) {
        return maxFontSize;
    }

    // Find the middle
    NSInteger fontSize = (minFontSize + maxFontSize) / 2;
    // Create the font
    UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
    // Create a constraint size with max height
    CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
    // Find label size for current font size
    CGRect rect = [label.text boundingRectWithSize:constraintSize
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                        attributes:@{NSFontAttributeName : font}
                                           context:nil];
    CGSize labelSize = rect.size;

    // EDIT:  The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
    if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
        return fontSize;
    } else if (labelSize.height > size.height || labelSize.width > size.width) {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
    } else {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
    }
}
person Niels Castle    schedule 09.06.2010
comment
После поиска часов и часов это было единственным решением, которое сработало для меня. Мне просто интересно, какова производительность цикла while, когда у вас есть 50-100 меток в представлении. - person agarcian; 31.01.2013
comment
У меня никогда не было причин его оптимизировать. Я не использую его для пересчета снова и снова. Единственное место, где у меня есть много меток по размеру, я выбираю для этого самый длинный текст и размер метки. - person Niels Castle; 04.03.2014
comment
@NielsCastle: ваше решение также применяет технику переноса слов? Я написал дело в ответе агарциана. Выполняет ли он и этот случай? - person Shahid Iqbal; 19.11.2014
comment
Я использую его для коротких этикеток. YMMW. Попробуйте установить lineBreakMode для входящей метки. - person Niels Castle; 19.11.2014
comment
Это ужасно неэффективно, особенно при изменении размера. - person Ky Leggiero; 19.08.2016

Я нашел ответ Нильса лучшим ответом на этот вопрос. Однако у меня есть UIView, который может иметь 100 меток, где мне нужно разместить текст, поэтому этот процесс был очень неэффективным, и я мог почувствовать падение производительности.

Вот его код, модифицированный для использования вместо линейного поиска бинарного поиска. Теперь он работает очень эффективно.

- (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
    // If the sizes are incorrect, return 0, or error, or an assertion.
    if (maxFontSize < minFontSize) {
        return 0;
    }

    // Find the middle
    NSInteger fontSize = (minFontSize + maxFontSize) / 2;
    // Create the font
    UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
    // Create a constraint size with max height
    CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
    // Find label size for current font size
    CGRect rect = [label.text boundingRectWithSize:constraintSize
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                        attributes:@{NSFontAttributeName : font}
                                           context:nil];
    CGSize labelSize = rect.size;

    // EDIT:  The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
    if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
        return fontSize;
    } else if (labelSize.height > size.height || labelSize.width > size.width) {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
    } else {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
    }
}

- (void)sizeBinaryLabel:(UILabel *)label toRect:(CGRect)labelRect {

    // Set the frame of the label to the targeted rectangle
    label.frame = labelRect;

    // Try all font sizes from largest to smallest font
    int maxFontSize = 300;
    int minFontSize = 5;

    NSInteger size = [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:maxFontSize withSize:label.frame.size];

    label.font = [UIFont fontWithName:label.font.fontName size:size];

}

Кредит также принадлежит https://gist.github.com/988219.

person agarcian    schedule 02.02.2013
comment
+1 - я как раз собирался написать решение для бинарного поиска и прокрутил вниз только для того, чтобы узнать, что бинарное решение уже было опубликовано! Это определенно лучшее решение для большинства случаев. Шикарный агарец. - person Pavan; 15.02.2013
comment
Спасибо, Паван. В этом красота СО. Сотрудничество сэкономит нам часы и часы разработки :-) - person agarcian; 15.02.2013
comment
Я просто добавил редактирование к ответу, потому что с предыдущим кодом некоторые слишком тонкие метки не отображали текст полностью внутри метки. Изменения в коде включают проверку не только высоты, но и ширины. - person agarcian; 23.03.2013
comment
Ммм, код @agarcian всегда дает мне 0. maxFontSize и minFontSize становятся равными без достижения первого if-then. Даже в самом простом тестовом проекте. Код Нильса работает нормально, хотя и медленнее. - person borked; 02.06.2013
comment
Это очень неточно для очень длинных текстов и очень коротких текстов. - person hfossli; 18.10.2013
comment
Хотя это быстрее, это не надежное решение. Также использует API, которые устарели с iOS 7. - person Leandros; 15.04.2014
comment
Однако я улучшил ответ, чтобы использовать неустаревшие API и не изменять текущий используемый шрифт. - person Leandros; 15.04.2014
comment
@Leandros: я использую этот код в рабочем приложении в магазине приложений. Это работает нормально. Почему вы говорите, что это не надежное решение? Это, безусловно, работает для моего варианта использования, который включает в себя короткие фразы на этикетках; а именно номера для выносок над графикой. Каков ваш вариант использования? - person agarcian; 18.04.2014
comment
@agarcian Я попробовал твое решение. Он работает, но не выполняет перенос слов. Например, если у меня есть текст метки Интернационализация. Ширина моего фрейма UILabel составляет 60 пикселей, а количество строк — 2. Теперь он должен переносить текст в одну строку с минимально возможным шрифтом. Но он сохраняет текст в 2 строки с немного большим шрифтом, и текст разбивается, как Internationali, в первой строке и sation во второй. Как мы можем применить перенос слов в нем. Пожалуйста, ответь. - person Shahid Iqbal; 19.11.2014
comment
Вы хотели поместить все И в первый оператор if или отсутствует ИЛИ? - person liquidpenguins; 27.01.2015

Вот версия Swift в соответствии с ответом @NielsCastle с использованием двоичного поиска

extension UILabel{

    func adjustFontSizeToFitRect(rect : CGRect){

        if text == nil{
            return
        }

        frame = rect

        let maxFontSize: CGFloat = 100.0
        let minFontSize: CGFloat = 5.0

        var q = Int(maxFontSize)
        var p = Int(minFontSize)

        let constraintSize = CGSize(width: rect.width, height: CGFloat.max)

        while(p <= q){
            let currentSize = (p + q) / 2
            font = font.fontWithSize( CGFloat(currentSize) )
            let text = NSAttributedString(string: self.text!, attributes: [NSFontAttributeName:font])
            let textRect = text.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, context: nil)

            let labelSize = textRect.size

            if labelSize.height < frame.height && labelSize.height >= frame.height-10 && labelSize.width < frame.width && labelSize.width >= frame.width-10 {
                break
            }else if labelSize.height > frame.height || labelSize.width > frame.width{
                q = currentSize - 1
            }else{
                p = currentSize + 1
            }
        }

    }
}

использование

label.adjustFontSizeToFitRect(rect)

часто просто

label.adjustFontSizeToFitRect(rect.frame)
person nRewik    schedule 11.07.2015

Это решение (на основе этого ответа) работает с автоматическим макетом и выполняет двоичный поиск для нахождения наилучшего размера шрифта.

Единственное предостережение, которое я обнаружил, заключается в том, что вы не можете указать количество строк (потому что, насколько я знаю, вы не можете сказать boundingRectWithSize, сколько строк вам нужно).

AdjustableLabel.h

#import <UIKit/UIKit.h>

@interface AdjustableLabel : UILabel
/** 
  If set to YES, font size will be automatically adjusted to frame.
  Note: numberOfLines can't be specified so it will be set to 0.
*/
@property(nonatomic) BOOL adjustsFontSizeToFitFrame;
@end

AdjustableLabel.m

#import "AdjustableLabel.h"

@interface AdjustableLabel ()
@property(nonatomic) BOOL fontSizeAdjusted;
@end

// The size found S satisfies: S fits in the frame and and S+DELTA doesn't.
#define DELTA 0.5

@implementation AdjustableLabel

- (void)setAdjustsFontSizeToFitFrame:(BOOL)adjustsFontSizeToFitFrame
{
    _adjustsFontSizeToFitFrame = adjustsFontSizeToFitFrame;

    if (adjustsFontSizeToFitFrame) {
        self.numberOfLines = 0; // because boundingRectWithSize works like this was 0 anyway
    }
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.adjustsFontSizeToFitFrame && !self.fontSizeAdjusted)
    {
        self.fontSizeAdjusted = YES; // to avoid recursion, because adjustFontSizeToFrame will trigger this method again

        [self adjustFontSizeToFrame];
    }
}

- (void) adjustFontSizeToFrame
{
    UILabel* label = self;

    if (label.text.length == 0) return;

    // Necessary or single-char texts won't be correctly adjusted
    BOOL checkWidth = label.text.length == 1;

    CGSize labelSize = label.frame.size;

    // Fit label width-wise
    CGSize constraintSize = CGSizeMake(checkWidth ? MAXFLOAT : labelSize.width, MAXFLOAT);

    // Try all font sizes from largest to smallest font size
    CGFloat maxFontSize = 300;
    CGFloat minFontSize = 5;

    NSString* text = label.text;
    UIFont* font = label.font;

    while (true)
    {
        // Binary search between min and max
        CGFloat fontSize = (maxFontSize + minFontSize) / 2;

        // Exit if approached minFontSize enough
        if (fontSize - minFontSize < DELTA/2) {
            font = [UIFont fontWithName:font.fontName size:minFontSize];
            break; // Exit because we reached the biggest font size that fits
        } else {
            font = [UIFont fontWithName:font.fontName size:fontSize];
        }

        // Find label size for current font size
        CGRect rect = [text boundingRectWithSize:constraintSize
                                         options:NSStringDrawingUsesLineFragmentOrigin
                                      attributes:@{NSFontAttributeName : font}
                                         context:nil];

        // Now we discard a half
        if( rect.size.height <= labelSize.height && (!checkWidth || rect.size.width <= labelSize.width) ) {
            minFontSize = fontSize; // the best size is in the bigger half
        } else {
            maxFontSize = fontSize; // the best size is in the smaller half
        }
    }

    label.font = font;
}

@end

Использование

AdjustableLabel* label = [[AdjustableLabel alloc] init];
label.adjustsFontSizeToFitFrame = YES;

// In case you change the font, the size you set doesn't matter
label.font = [UIFont fontWithName:@"OpenSans-Light" size:20];
person Ferran Maylinch    schedule 11.11.2015
comment
Этот. Работает. Отлично. - person Fattie; 17.05.2016
comment
Это было очень элегантно и работает очень хорошо. У меня было одно небольшое улучшение, которое позволяет использовать текст метки в виде одного слова. В этой ситуации вы не хотите, чтобы он был разбит на строки, поэтому я добавил: // If there is only one word, then reduce the height so as to force boundingRectWithSize to fit it on // one line. // if ([label.text componentsSeparatedByString:@" "].count == 1) { `labelSize.height /= 2.0;` } сразу после объявления labelSize. - person PKCLsoft; 02.11.2017

Вот расширение Swift для UILabel. Он запускает алгоритм бинарного поиска для изменения размера шрифта и границ метки и протестирован для работы с iOS 12.

ПРИМЕНЕНИЕ: изменяет размер шрифта, чтобы он соответствовал размеру 100 x 100 (с точностью до 1,0 пункта шрифта), и выравнивает его по верхнему краю.

let adjustedSize = <label>.fitFontForSize(CGSizeMake(100, 100))
<label>.frame = CGRect(x: 0, y: 0, width: 100, height: adjustedSize.height)

Скопируйте/вставьте следующее в свой файл:

extension UILabel {
    @discardableResult func fitFontForSize(_ constrainedSize: CGSize,
                                           maxFontSize: CGFloat = 100,
                                           minFontSize: CGFloat = 5,
                                           accuracy: CGFloat = 1) -> CGSize {
        assert(maxFontSize > minFontSize)

        var minFontSize = minFontSize
        var maxFontSize = maxFontSize
        var fittingSize = constrainedSize

        while maxFontSize - minFontSize > accuracy {
            let midFontSize: CGFloat = ((minFontSize + maxFontSize) / 2)
            font = font.withSize(midFontSize)
            fittingSize = sizeThatFits(constrainedSize)
            if fittingSize.height <= constrainedSize.height
                && fittingSize.width <= constrainedSize.width {
                minFontSize = midFontSize
            } else {
                maxFontSize = midFontSize
            }
        }

        return fittingSize
    }
} 

Эта функция не изменит размер метки, она влияет только на свойство font. Вы можете использовать возвращенное значение размера для настройки макета этикетки.

person Avi Frankl    schedule 31.12.2015
comment
Хорошее расширение;) Имейте в виду, что параметр var в объявлении функции фактически устарел и будет удален в Swift 3. - person rafalkitta; 24.05.2016
comment
Я внес несколько улучшений в этот фрагмент. Теперь он обновлен до современного Swift и работает на iOS 12. Кроме того, я заменил sizeToFit на sizeThatFits, поэтому вызов этого метода не влияет на рамку метки и довольно хорошо работает с ограничениями. Также я добавил удобное возвращаемое значение, которое можно использовать для дальнейшей настройки макета. Некоторые избыточные вызовы удалены. - person kelin; 11.04.2019

Если кто-то ищет реализацию MonoTouch/Xamarin.iOS, как это сделал я... вот вам:

private int BinarySearchForFontSizeForText(NSString text, int minFontSize, int maxFontSize, SizeF size)
{
    if (maxFontSize < minFontSize) 
        return minFontSize;

    int fontSize = (minFontSize + maxFontSize) / 2;
    UIFont font = UIFont.BoldSystemFontOfSize(fontSize);

    var constraintSize = new SizeF(size.Width, float.MaxValue);
    SizeF labelSize = text.StringSize(font, constraintSize, UILineBreakMode.WordWrap);

    if (labelSize.Height >= size.Height + 10 && labelSize.Width >= size.Width + 10 && labelSize.Height <= size.Height && labelSize.Width <= size.Width)
        return fontSize;
    else if (labelSize.Height > size.Height || labelSize.Width > size.Width) 
        return BinarySearchForFontSizeForText(text, minFontSize, fontSize - 1, size);
    else 
        return BinarySearchForFontSizeForText(text, fontSize + 1, maxFontSize, size);
}

private void SizeLabelToRect(UILabel label, RectangleF labelRect)
{
    label.Frame = labelRect;

    int maxFontSize = 300;
    int minFontSize = 5;
    int size = BinarySearchForFontSizeForText(new NSString(label.Text), minFontSize, maxFontSize, label.Frame.Size);

    label.Font = UIFont.SystemFontOfSize(size);
}

Это перевод кода agarcian с Objective-C на C# с небольшой модификацией: поскольку возвращаемый результат всегда был 0 (см. комментарий borked) Я возвращаю рассчитанный minFontSize, что дает правильный размер шрифта.

person asp_net    schedule 17.08.2013
comment
Большое спасибо за небольшую модификацию. - person Rudolf Adamkovič; 30.04.2014

Также установите myLabel.numberOfLines = 10 или любое максимальное количество строк, которое вы хотите.

person Pyro2927    schedule 16.05.2010
comment
Или просто 0 для бесконечного количества строк - person ; 21.09.2011
comment
^ Этот парень знает, о чем говорит - person GangstaGraham; 26.08.2013

Все это интересные решения исходной проблемы, однако все они также упускают важную вещь: если вы полагаетесь исключительно на familyName, чтобы получить следующий шрифт для тестирования, вы теряете информацию о весе. и, возможно, более сложные атрибуты, такие как маленькие заглавные буквы, стиль рисунка и т. д.

Лучший подход — вместо того, чтобы передавать имя шрифта и выполнять [UIFont fontWithName:someFontName size:someFontSize], передавать UIFontDescriptor объектов, а затем выполнять [UIFont fontWithDescriptor:someFontDescriptor size:someFontSize].

person DrMickeyLauer    schedule 28.02.2016

Поскольку я не нашел рабочего решения, отвечающего всем моим потребностям, используя приведенные выше ответы, я создал свои собственные компоненты, обеспечивающие следующие функции: FittableFontLabel

  • Отрегулируйте шрифт по высоте и ширине при использовании многострочной метки
  • Отрегулируйте шрифт по ширине при использовании однострочной метки, метка высоты изменит размер
  • Поддержка NSAttributedStrings, а также основной строки
  • Автоматическая настройка размера при изменении текста/рамки метки
  • ...

Если кому-то из вас интересно, это полная быстрая библиотека, доступная с использованием CocoaPods: https://github.com/tbaranes/FittableFontLabel

person tbaranes    schedule 27.03.2016

Поиск работы с кодом Нильса Кастла.

Вот та же идея, но с другой реализацией.
Мое решение более точное, но и гораздо более требовательное к процессору.

Добавьте эту функцию в класс, наследующий UILabel.

-(void)fitCurrentFrame{

    CGSize iHave = self.frame.size;

    BOOL isContained = NO;
    do{
        CGSize iWant = [self.text sizeWithFont:self.font];
        if(iWant.width > iHave.width || iWant.height > iHave.height){
            self.font = [UIFont fontWithName:self.font.fontName size:self.font.pointSize - 0.1];
            isContained = NO;
        }else{
            isContained = YES;
        }

    }while (isContained == NO);
}
person Martin Magakian    schedule 14.06.2013

Я создал категорию для UILabel на основе ответа @agarcian. Но я вычисляю fontSize в зависимости от квадрата, необходимого на экране для рисования текста. Этот метод не требует циклов, а расчет выполняется за одну итерацию.

Вот файл .h:

//  UILabel+Extended.h
//  Created by Firuz on 16/08/14.
//  Copyright (c) 2014. All rights reserved.

#import <UIKit/UIKit.h>

@interface UILabel (Extended)

/** This method calculate the optimal font size for current number of lines in UILable. Mus be called after drawing UILabel view */
- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize;

@end

А вот файл .m:

//  UILabel+Extended.m
//  Created by Firuz on 16/08/14.
//  Copyright (c) 2014. All rights reserved.

#import "UILabel+Extended.h"

@implementation UILabel (Extended)

- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize
{
    if (maxFontSize < minFontSize) {
        return 0;
    }

    UIFont *font = [UIFont fontWithName:self.font.fontName size:maxFontSize];

    CGFloat lineHeight = [font lineHeight];

    CGSize constraintSize = CGSizeMake(MAXFLOAT, lineHeight);

    CGRect rect = [self.text boundingRectWithSize:constraintSize
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                        attributes:@{NSFontAttributeName : font}
                                           context:nil];

    CGFloat labelSqr = self.frame.size.width * self.frame.size.height;
    CGFloat stringSqr = rect.size.width/self.frame.size.width * (lineHeight + font.pointSize) * self.frame.size.width;

    CGFloat multiplyer = labelSqr/stringSqr;

    if (multiplyer < 1) {
        if (minFontSize < maxFontSize*multiplyer) {
            return maxFontSize * multiplyer;
        } else {
            return minFontSize;
        }
    }
    return maxFontSize;
}

@end
person fir    schedule 16.08.2014

Все бинарные поиски хороши, но останавливать рекурсию с помощью проверки кадров не так логично. Лучше проверьте размер шрифта, потому что UIFont поддерживает размер с плавающей запятой, и этот шрифт больше подходит. Плюс использование стиля абзаца метки для более точного расчета размера.

Если кому-то интересно, можете посмотреть код ниже:

static UIFont * ___suitableFontInRangePrivate(const CGSize labelSize,
                                            NSParagraphStyle * paragraphStyle,
                                            NSString * fontName,
                                            NSString * text,
                                            const CGFloat minSize,
                                            const CGFloat maxSize)
{
    // Font size in range, middle size between max & min.
    const CGFloat currentSize = minSize + ((maxSize - minSize) / 2);

    // Font with middle size.
    UIFont * currentFont = [UIFont fontWithName:fontName size:currentSize];

    // Calculate text height.
    const CGFloat textHeight = [text boundingRectWithSize:CGSizeMake(labelSize.width, CGFLOAT_MAX)
                                                options:NSStringDrawingUsesLineFragmentOrigin
                                            attributes:@{ NSFontAttributeName : currentFont, NSParagraphStyleAttributeName : paragraphStyle }
                                                context:nil].size.height;
    CGFloat min, max;
    if (textHeight > labelSize.height)
    {
        // Take left range part.
        min = minSize;
        max = currentSize;
    }
    else
    {
        // Take right range part.
        min = currentSize;
        max = maxSize;
    }

    // If font size in int range [0.0; 2.0] - got it, othervice continue search.
    return ((max - min) <= 2.0) ? currentFont : ___suitableFontInRangePrivate(labelSize, paragraphStyle, fontName, text, min, max);
}

void UILabelAdjustsFontSizeToFrame(UILabel * label)
{
    if (!label) return;

    NSString * text = [label text];

    __block NSParagraphStyle * style = nil;
    [[label attributedText] enumerateAttributesInRange:NSMakeRange(0, [text length])
                                            options:(NSAttributedStringEnumerationOptions)0
                                            usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop){
                                                id paragraphStyle = [attrs objectForKey:@"NSParagraphStyle"];
                                                if (paragraphStyle) style = [paragraphStyle retain];
                                            }];

    if (!style)
    {
        NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init];
        if (paragraphStyle)
        {
            [paragraphStyle setLineBreakMode:[label lineBreakMode]];
            [paragraphStyle setAlignment:[label textAlignment]];
        }
        style = paragraphStyle;
    }

    UIFont * suitableFont = ___suitableFontInRangePrivate([label frame].size, style, [[label font] fontName], text, 0, 500);
    [label setFont:suitableFont];
    [style release];
}
person Oleh Kulykov    schedule 02.03.2015

«Решение для бинарного поиска» Swift 3 на основе этого ответа с небольшими улучшениями. Образец находится в контексте подкласса 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)
    }
}

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

person tadija    schedule 14.11.2016
comment
Это не работает должным образом. Поменяйте местами greatestFiniteMagnitude на ширину, например CGSize(width: bounds.width, height: .greatestFiniteMagnitude) - person zrubenst; 02.07.2017

Ответ @agarcian был близок, но он не совсем сработал для меня, поскольку кто-то еще упомянул в комментарии, он всегда возвращал 0.

Вот моя попытка.

Ваше здоровье!

/**
 * Returns the font size required in order to fit the specified text in the specified area.
 * NB! When drawing, be sure to pass in the same options that we pass to boundingRectWithSize:options:attributes:context:
 * Heavily modified form of: http://stackoverflow.com/a/14662750/1027452
 */
+(NSInteger)fontSizeForText:(NSString *)text withFont:(UIFont *)font inArea:(CGSize)areaSize minFontSize:(NSInteger)minFontSize maxFontSize:(NSInteger)maxFontSize
{
// If the sizes are incorrect, return 0, or error, or an assertion.
    if (maxFontSize < minFontSize) {
        return 0;
    }

    // Find the middle
    NSInteger fontSize = (minFontSize + maxFontSize) / 2;
    // Create the font
    UIFont *f = [UIFont fontWithName:font.fontName size:fontSize];
    // Create a constraint size with max height
    CGSize constraintSize = CGSizeMake(areaSize.width, MAXFLOAT);
    // Find label size for current font size
    CGRect rect = [text boundingRectWithSize:constraintSize
                                           options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                        attributes:@{NSFontAttributeName : f}
                                           context:nil];
    CGSize labelSize = rect.size;

    if (labelSize.height <= areaSize.height && labelSize.width  <= areaSize.width )
    {
        return fontSize;
    }
    else if (labelSize.height > areaSize.height || labelSize.width > areaSize.width)
    {
        return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize maxFontSize:maxFontSize -1];;
    }
    else
    {
        return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize+1 maxFontSize:maxFontSize];;
    }
}
person Chris Birch    schedule 29.01.2017