Как настроить размер шрифта надписи под прямоугольник? - PullRequest
36 голосов
/ 16 мая 2010

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

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

Ответы [ 14 ]

49 голосов
/ 09 июня 2010

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

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

- (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];
    }
}
31 голосов
/ 02 февраля 2013

Я нашел ответ Нильса лучшим ответом на этот вопрос. Тем не менее, у меня есть 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

8 голосов
/ 11 июля 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)
5 голосов
/ 11 ноября 2015

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

Единственное замечание, которое я нашел, - это то, что вы можетеНе указывайте количество строк (потому что AFAIK вы не можете сказать 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];
3 голосов
/ 31 декабря 2015

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

ИСПОЛЬЗОВАНИЕ: Изменяет размер шрифта до размера 100x100 (с точностью до точки шрифта 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. Вы можете использовать возвращаемое значение размера, чтобы настроить расположение метки.

2 голосов
/ 17 августа 2013

Если кто-то ищет реализацию 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 ). приводит к правильному размеру шрифта.

1 голос
/ 27 марта 2016

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

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

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

1 голос
/ 28 февраля 2016

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

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

1 голос
/ 16 мая 2010

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

0 голосов
/ 29 января 2017

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

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

Ура! * * 1005

/**
 * 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];;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...