Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

Этот фрагмент начинается с 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);
}

Ответ 2

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

Ответ 3

Здесь версия 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)

Ответ 4

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

Единственное предостережение, которое я обнаружил, заключается в том, что вы не можете указать количество строк (потому что 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];

Ответ 5

Если кто-то ищет реализацию 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);
}

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

Ответ 6

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

ИСПОЛЬЗОВАНИЕ: Изменяет размер шрифта до размера 100x100 (точнее в пределах 1.0 точки шрифта).

<label>.fitFontForSize(CGSizeMake(100.0, 100.0))

Скопировать/Вставить в свой файл

следующее:
extension UILabel {

    func fitFontForSize(constrainedSize : CGSize, var maxFontSize : CGFloat = 300.0, var minFontSize : CGFloat = 5.0, accuracy : CGFloat = 1.0) {
        assert(maxFontSize > minFontSize)
        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()
    } 

} 

Примечание: Ограничения ширины и высоты метки не должны быть установлены, чтобы это работало. Легкая работа вокруг заключается в том, чтобы вставить ярлык в UIView, который установил ограничения по размеру, а затем вызвать функцию, определенную выше (как показано ниже).

<label>.fitFontForSize(<superview>.bounds.size)

Ответ 7

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

Ответ 8

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

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

Ответ 9

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


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

Добавьте эту функцию в класс, который наследует 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);
}

Ответ 10

Я создал категорию для 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

Ответ 11

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

Ответ 12

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

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

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

Ответ 13

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)
    }
}

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

Ответ 14

Ответ на @ararcian был близок, но для меня это не совсем сработало, так как кто-то еще упомянул в комментарии, он всегда возвращал 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];;
    }
}