Динамически изменяемый размер шрифта UILabel

188

В настоящее время у меня есть UILabel:

factLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, 280, 100)];
factLabel.text = @"some text some text some text some text";
factLabel.backgroundColor = [UIColor clearColor];
factLabel.lineBreakMode = UILineBreakModeWordWrap;
factLabel.numberOfLines = 10;
[self.view addSubview:factLabel];

На протяжении всей жизни моего iOS-приложения, factLabelполучается куча разных ценностей. Некоторые с несколькими предложениями, другие только с 5 или 6 словами.

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

CodeGuy
источник
2
В 2016 году я действительно верю, что единственным хорошим решением является использование подхода «использовать автоусадку». Сделайте для поля UILabel нужный вам размер, сделайте так, чтобы шрифт заполнял UILabel, выберите автоматическое сжатие, установите огромный размер шрифта (300) и обязательно тестируйте на самых маленьких / самых больших симуляторах. (Итак, 4s / PadPro в настоящее время.) Полное объяснение: stackoverflow.com/a/35154493/294884 Это единственное реальное решение на сегодняшний день.
Толстяк

Ответы:

370

Одна линия:

factLabel.numberOfLines = 1;
factLabel.minimumFontSize = 8;
factLabel.adjustsFontSizeToFitWidth = YES;

Приведенный выше код будет корректировать размер шрифта вашего текста до (например), 8пытаясь вписать ваш текст в метку. numberOfLines = 1является обязательным.

Несколько строк:

Ибо numberOfLines > 1есть метод для определения размера конечного текста с помощью методов добавления NSString sizeWithFont: ... UIKit , например:

CGSize lLabelSize = [yourText sizeWithFont:factLabel.font
                                  forWidth:factLabel.frame.size.width
                             lineBreakMode:factLabel.lineBreakMode];

После этого вы можете просто изменить размер вашей метки lLabelSize, например, используя result (при условии, что вы измените только высоту метки):

factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, factLabel.frame.size.width, lLabelSize.height);

iOS6

Одна линия:

Начиная с iOS6, minimumFontSizeустарела. Линия

factLabel.minimumFontSize = 8.;

можно изменить на:

factLabel.minimumScaleFactor = 8./factLabel.font.pointSize;

IOS 7

Несколько строк:

Начиная с iOS7, sizeWithFontстановится устаревшим. Многострочный регистр сводится к:

factLabel.numberOfLines = 0;
factLabel.lineBreakMode = NSLineBreakByWordWrapping;
CGSize maximumLabelSize = CGSizeMake(factLabel.frame.size.width, CGFLOAT_MAX);
CGSize expectSize = [factLabel sizeThatFits:maximumLabelSize];
factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, expectSize.width, expectSize.height);

iOS 13 (Swift 5):

label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.5
Мартин Бабакаев
источник
но это помещает текст все в одну строку. и если я изменю factLabel.numberOfLines, то размер шрифта не изменится динамически.
CodeGuy
@ reising1: ты прав. Это просто то, как сделать фреймворк для работы по изменению размера за вас.
Мартин Бабакаев
так что ответ на мой вопрос заключается в том, что нет никакого способа сделать это, используя предоставленные рамки?
CodeGuy
1
@ reising1: в этом случае вы также можете использовать метод добавления NSString UIKit: sizeWithFont:constrainedToSize:lineBreakMode:но этот путь немного сложен
Мартин Бабакаев
6
Это устарело с iOS6. Замените его наmyLabel.minimumScaleFactor:10.0/[UIFont labelFontSize];
Норберт
72

minimumFontSizeустарела с iOS 6. Вы можете использовать minimumScaleFactor.

yourLabel.adjustsFontSizeToFitWidth=YES;
yourLabel.minimumScaleFactor=0.5;

Это позаботится о размере шрифта в соответствии с шириной метки и текста.

Амит Сингх
источник
Я обычно использую 0,8, потому что даже 0,7 выглядит слишком маленьким. Конечно, некоторый текст может не соответствовать минимальному масштабному коэффициенту 0,8, это вопрос выбора того, что выглядит лучше и где вещи становятся нечитаемыми. OTOH мои приложения могут быть повернуты, что очень помогает.
gnasher729
adjustsFontSizeToFitWidthсокращает текст, только если он не помещается в контейнере
user25
24

На основе ответа @Eyal Ben Dov вы можете создать категорию, чтобы сделать ее гибкой для использования в других ваших приложениях.

Обс .: я обновил его код, чтобы сделать его совместимым с iOS 7

Заголовочный файл

#import <UIKit/UIKit.h>

@interface UILabel (DynamicFontSize)

-(void) adjustFontSizeToFillItsContents;

@end

-Информационный файл

#import "UILabel+DynamicFontSize.h"

@implementation UILabel (DynamicFontSize)

#define CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE 35
#define CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE 3

-(void) adjustFontSizeToFillItsContents
{
    NSString* text = self.text;

    for (int i = CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE; i>CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE; i--) {

        UIFont *font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
        NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: font}];

        CGRect rectSize = [attributedText boundingRectWithSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil];

        if (rectSize.size.height <= self.frame.size.height) {
            self.font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
            break;
        }
    }

}

@end

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

#import "UILabel+DynamicFontSize.h"

[myUILabel adjustFontSizeToFillItsContents];

ура

Пауло Мигель Алмейда
источник
это не работает для меня. Содержимое моей UILabel теперь отключено.
Адриан
1
Если это не работает для вас, вероятно, потому что рамка метки еще не установлена. Попробуйте установить кадр перед вызовом этого (или позвоните setNeedsLayout/, layoutIfNeededесли вы используете AutoLayout).
bmueller
Это дает следующий сбой "NSInvalidArgumentException, причина: 'NSConcreteAttributedString initWithString :: nil value'"
Мохамед Салех
Это означает, что ваша строка NSString не может быть нулевой. Я предполагаю, что если вы хотите настроить размер шрифта, чтобы заполнить содержимое UILabel, вы должны хотя бы предоставить текст.
Пауло Мигель Алмейда
Это имеет недостаток. Это разрывы строк между символами, поэтому вы видите слова, разделенные на разные строки. Есть ли способ обойти это?
Озгюр
24

Одна строка - Есть два способа, которые вы можете просто изменить.

1- Прагматично (Свифт 3)

Просто добавьте следующий код

    yourLabel.numberOfLines = 1;
    yourLabel.minimumScaleFactor = 0.7;
    yourLabel.adjustsFontSizeToFitWidth = true;

2 - Использование инспектора атрибутов UILabel

i- Select your label- Set number of lines 1.
ii- Autoshrink-  Select Minimum Font Scale from drop down
iii- Set Minimum Font Scale value as you wish , I have set 0.7 as in below image. (default is 0.5)

введите описание изображения здесь

Девендра Сингх
источник
22

Это 2015 год. Мне нужно было найти пост в блоге, где объяснялось бы, как это сделать для последней версии iOS и XCode с Swift, чтобы он работал с несколькими строками.

  1. установите «Autoshrink» на «Минимальный размер шрифта».
  2. установите самый большой желаемый размер шрифта (я выбрал 20)
  3. Измените «Разрывы строк» ​​с «Перенос слов» на «Обрезать хвост».

Источник: http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/

Завтра
источник
Это действительно спасатель жизни!
bhakti123
2
Очень круто .. Эта точка усечения является наиболее важной. Так как в случае автопоставки с переносом слов не ощущается стремление уменьшить размер шрифта, тогда как при его усечении автоперекрытие должно сохранять текст с блейда и то, что он изменяет размер шрифта.
GKK
12

Swift версия:

textLabel.adjustsFontSizeToFitWidth = true
textLabel.minimumScaleFactor = 0.5
Esqarrouth
источник
Спасибо .. Похоже, здесь последовательность также имеет значение
Pramod
7

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

ИСПОЛЬЗОВАНИЕ: Где <label>ваша предопределенная UILabel, которая требует изменения размера шрифта

<label>.fitFontForSize()

По умолчанию эта функция выполняет поиск в диапазоне размеров шрифта 5pt и 300pt и устанавливает шрифт так, чтобы его текст «идеально» соответствовал границам (с точностью до 1.0pt). Вы можете определить параметры так, чтобы он, например, выполнял точный поиск между 1pt и текущим размером шрифта метки в пределах 0.1pt следующим образом:

<label>.fitFontForSize(1.0, maxFontSize: <label>.font.pointSize, accuracy:0.1)

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

extension UILabel {

    func fitFontForSize(var minFontSize : CGFloat = 5.0, var maxFontSize : CGFloat = 300.0, accuracy : CGFloat = 1.0) {
        assert(maxFontSize > minFontSize)
        layoutIfNeeded() // Can be removed at your own discretion
        let constrainedSize = bounds.size
        while maxFontSize - minFontSize > accuracy {
            let midFontSize : CGFloat = ((minFontSize + maxFontSize) / 2)
            font = font.fontWithSize(midFontSize)
            sizeToFit()
            let checkSize : CGSize = bounds.size
            if  checkSize.height < constrainedSize.height && checkSize.width < constrainedSize.width {
                minFontSize = midFontSize
            } else {
                maxFontSize = midFontSize
            }
        }
        font = font.fontWithSize(minFontSize)
        sizeToFit()
        layoutIfNeeded() // Can be removed at your own discretion
    }

}

ПРИМЕЧАНИЕ. Каждый из layoutIfNeeded()вызовов можно удалить по своему усмотрению.

Ави франкл
источник
Ах, но на самом деле это не работает с autolayout; "sizeToFit" ничего не делают в этом случае.
Толстяк
4

Это немного не сложно, но это должно сработать, например, допустим, вы хотите ограничить вашу uilabel до 120x120, с максимальным размером шрифта 28:

magicLabel.numberOfLines = 0;
magicLabel.lineBreakMode = NSLineBreakByWordWrapping;
...
magicLabel.text = text;
    for (int i = 28; i>3; i--) {
        CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:(CGFloat)i] constrainedToSize:CGSizeMake(120.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
        if (size.height < 120) {
            magicLabel.font = [UIFont systemFontOfSize:(CGFloat)i];
            break;
        }
    }
Эяль Бен Дов
источник
Это кажется довольно неэффективным - вы должны позволить UILabel динамически подниматься, чтобы поместиться в некоторое предоставленное доступное пространство. Если вы запустите это для чего-то вроде расчета шрифта заголовка ячейки табличного представления, у вас возникнут серьезные проблемы с запаздыванием. Подход может работать, но определенно не рекомендуется.
Зорайр
Голосуйте за то, чтобы быть единственным человеком, который фактически ответил на вопрос.
Джай
2

Просто отправьте сообщение sizeToFit в UITextView. Он будет регулировать свою собственную высоту, чтобы просто соответствовать ее тексту. Он не изменит свою ширину или происхождение.

[textViewA1 sizeToFit];
codercat
источник
Что происходит, когда размер, который соответствует тексту, слишком велик для пространства контейнера? Например, предположим, что у вас есть 100 доступных точек, чтобы соответствовать текстовому представлению, после того, как sizeToFitвы textViewA1наберете 200 очков, вы получите обрезку.
Зорайр
0

Версия Swift 2.0:

private func adapteSizeLabel(label: UILabel, sizeMax: CGFloat) {
     label.numberOfLines = 0
     label.lineBreakMode = NSLineBreakMode.ByWordWrapping
     let maximumLabelSize = CGSizeMake(label.frame.size.width, sizeMax);
     let expectSize = label.sizeThatFits(maximumLabelSize)
     label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, expectSize.width, expectSize.height)
}
Фил
источник
0

Это решение работает для мультилинии:

Прочитав несколько статей и потребовав функцию, которая автоматически масштабировала бы текст и корректировала количество строк, чтобы наилучшим образом соответствовать заданному размеру метки, я сам написал функцию. (т. е. короткая строка прекрасно помещалась бы на одной строке и использовала бы большую часть рамки метки, тогда как длинная сильная сторона автоматически разбивалась на 2 или 3 строки и соответственно изменяла размер)

Не стесняйтесь повторно использовать это и настроить как требуется. Убедитесь, что вы вызываете его после viewDidLayoutSubviewsзавершения, чтобы был установлен начальный кадр метки.

+ (void)setFontForLabel:(UILabel *)label withMaximumFontSize:(float)maxFontSize andMaximumLines:(int)maxLines {
    int numLines = 1;
    float fontSize = maxFontSize;
    CGSize textSize; // The size of the text
    CGSize frameSize; // The size of the frame of the label
    CGSize unrestrictedFrameSize; // The size the text would be if it were not restricted by the label height
    CGRect originalLabelFrame = label.frame;

    frameSize = label.frame.size;
    textSize = [label.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize: fontSize]}];

    // Work out the number of lines that will need to fit the text in snug
    while (((textSize.width / numLines) / (textSize.height * numLines) > frameSize.width / frameSize.height) && (numLines < maxLines)) {
        numLines++;
    }

    label.numberOfLines = numLines;

    // Get the current text size
    label.font = [UIFont systemFontOfSize:fontSize];
    textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
                                        options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                     attributes:@{NSFontAttributeName : label.font}
                                        context:nil].size;

    // Adjust the frame size so that it can fit text on more lines
    // so that we do not end up with truncated text
    label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, label.frame.size.width, label.frame.size.width);

    // Get the size of the text as it would fit into the extended label size
    unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;

    // Keep reducing the font size until it fits
    while (textSize.width > unrestrictedFrameSize.width || textSize.height > frameSize.height) {
        fontSize--;
        label.font = [UIFont systemFontOfSize:fontSize];
        textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
                                            options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                         attributes:@{NSFontAttributeName : label.font}
                                            context:nil].size;
        unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;
    }

    // Set the label frame size back to original
    label.frame = originalLabelFrame;
}
aaroncatlin
источник
0

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

@interface SNTextLayer : CATextLayer

@end

@implementation SNTextLayer

- (void)drawInContext:(CGContextRef)ctx {
    // We override this to make text appear at the same vertical positon as in UILabel
    // (otherwise it's shifted tdown)
    CGFloat height = self.bounds.size.height;
    float fontSize = self.fontSize;
    // May need to adjust this somewhat if it's not aligned perfectly in your implementation
    float yDiff = (height-fontSize)/2 - fontSize/10;

    CGContextSaveGState(ctx);
    CGContextTranslateCTM(ctx, 0.0, yDiff);
    [super drawInContext:ctx];
     CGContextRestoreGState(ctx);
}

@end

@interface SNAnimatableLabel ()

@property CATextLayer* textLayer;

@end

@interface SNAnimatableLabel : UILabel

- (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration;

@end



@implementation SNAnimatableLabel


- (void)awakeFromNib {
    [super awakeFromNib];
    _textLayer = [SNTextLayer new];
    _textLayer.backgroundColor = self.backgroundColor.CGColor;
    _textLayer.foregroundColor = self.textColor.CGColor;
    _textLayer.font = CGFontCreateWithFontName((CFStringRef)self.font.fontName);
    _textLayer.frame = self.bounds;
    _textLayer.string = self.text;
    _textLayer.fontSize = self.font.pointSize;
    _textLayer.contentsScale = [UIScreen mainScreen].scale;
    [_textLayer setPosition: CGPointMake(CGRectGetMidX(_textLayer.frame), CGRectGetMidY(_textLayer.frame))];
    [_textLayer setAnchorPoint: CGPointMake(0.5, 0.5)];
    [_textLayer setAlignmentMode: kCAAlignmentCenter];
    self.textColor = self.backgroundColor;
    // Blend text with background, so that it doens't interfere with textlayer text
    [self.layer addSublayer:_textLayer];
    self.layer.masksToBounds = NO;
}

- (void)setText:(NSString *)text {
    _textLayer.string = text;
    super.text = text;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    // Need to enlarge the frame, otherwise the text may get clipped for bigger font sizes
    _textLayer.frame = CGRectInset(self.bounds, -5, -5);
}

- (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration {
    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];
    _textLayer.fontSize = fontSize;
    [CATransaction commit];
}
videolist
источник