it-swarm.com.ru

Многострочный UILabel с AdjusttsFontSizeToFitWidth

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

К сожалению, согласно документации, свойство adjustsFontSizeToFitWidth «действует только тогда, когда для свойства numberOfLines установлено значение 1».

Я попытался определить настроенный размер шрифта, используя

-[NSString (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode]

и затем уменьшая размер шрифта, пока он не уместится. К сожалению, этот метод внутренне усекает текст до указанного размера и возвращает размер полученной усеченной строки.

41
Ortwin Gentz

В этот вопрос , 0x90 предоставляет решение, которое - хотя и немного уродливо - делает то, что я хочу. В частности, он правильно обрабатывает ситуацию, когда отдельное слово не соответствует ширине при начальном размере шрифта. Я немного изменил код, чтобы он работал как категория на NSString:

- (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
    CGFloat fontSize = [font pointSize];
    CGFloat height = [self sizeWithFont:font constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
    UIFont *newFont = font;

    //Reduce font size while too large, break if no height (empty string)
    while (height > size.height && height != 0) {   
        fontSize--;  
        newFont = [UIFont fontWithName:font.fontName size:fontSize];   
        height = [self sizeWithFont:newFont constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
    };

    // Loop through words in string and resize to fit
    for (NSString *Word in [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
        CGFloat width = [Word sizeWithFont:newFont].width;
        while (width > size.width && width != 0) {
            fontSize--;
            newFont = [UIFont fontWithName:font.fontName size:fontSize];   
            width = [Word sizeWithFont:newFont].width;
        }
    }
    return fontSize;
}

Чтобы использовать его с UILabel:

    CGFloat fontSize = [label.text fontSizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:label.frame.size];
    label.font = [UIFont boldSystemFontOfSize:fontSize];

EDIT: исправлен код для инициализации newFont с font. Исправляет сбой при определенных обстоятельствах.

50
Ortwin Gentz

В некоторых случаях изменение «Разрывов строк» ​​с «Перенос слов» на «Обрезать хвост» может оказаться всем необходимым, если вы знаете, сколько строк вы хотите (например, «2»): Кредит: Бекки Хансмейер

5
weienw

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

час:

@interface EPCLabel : UILabel {
    float originalPointSize;
    CGSize originalSize;
}

@property (nonatomic, readwrite) BOOL alignTextOnTop;
@end

м:

#import "EPCLabel.h"

@implementation EPCLabel
@synthesize alignTextOnTop;

-(void)verticalAlignTop {
    CGSize maximumSize = originalSize;
    NSString *dateString = self.text;
    UIFont *dateFont = self.font;
    CGSize dateStringSize = [dateString sizeWithFont:dateFont 
                                   constrainedToSize:CGSizeMake(self.frame.size.width, maximumSize.height)
                                       lineBreakMode:self.lineBreakMode];

    CGRect dateFrame = CGRectMake(self.frame.Origin.x, self.frame.Origin.y, self.frame.size.width, dateStringSize.height);

    self.frame = dateFrame;
}

- (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
    CGFloat fontSize = [font pointSize];
    CGFloat height = [self.text sizeWithFont:font             
                           constrainedToSize:CGSizeMake(size.width,FLT_MAX)  
                               lineBreakMode:UILineBreakModeWordWrap].height;
    UIFont *newFont = font;

    //Reduce font size while too large, break if no height (empty string)
    while (height > size.height && height != 0 && fontSize > self.minimumFontSize) { 
        fontSize--;  
        newFont = [UIFont fontWithName:font.fontName size:fontSize];   
        height = [self.text sizeWithFont:newFont  
                       constrainedToSize:CGSizeMake(size.width,FLT_MAX) 
                           lineBreakMode:UILineBreakModeWordWrap].height;
    };

    // Loop through words in string and resize to fit
    if (fontSize > self.minimumFontSize) {
        for (NSString *Word in [self.text componentsSeparatedByString:@" "]) {
            CGFloat width = [Word sizeWithFont:newFont].width;
            while (width > size.width && width != 0 && fontSize > self.minimumFontSize) {
                fontSize--;
                newFont = [UIFont fontWithName:font.fontName size:fontSize];   
                width = [Word sizeWithFont:newFont].width;
            }
        }
    }
    return fontSize;
}

-(void)setText:(NSString *)text {
    [super setText:text];
    if (originalSize.height == 0) {
        originalPointSize = self.font.pointSize;
        originalSize = self.frame.size;
    }

    if (self.adjustsFontSizeToFitWidth && self.numberOfLines > 1) {
        UIFont *origFont = [UIFont fontWithName:self.font.fontName size:originalPointSize];
        self.font = [UIFont fontWithName:origFont.fontName size:[self fontSizeWithFont:origFont constrainedToSize:originalSize]];
    }

    if (self.alignTextOnTop) [self verticalAlignTop];
}

-(void)setAlignTextOnTop:(BOOL)flag {
    alignTextOnTop = YES;
    if (alignTextOnTop && self.text != nil)
        [self verticalAlignTop];
}

@end

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

3
Everton Cunha

Для полностью рабочего решения, смотрите в нижней части моего ответа ????

Чтобы вручную измерить размеры text/attributedText вашего UILabel, чтобы найти подходящий размер шрифта, используя вашу собственную стратегию, у вас есть несколько вариантов:

  1. Используйте функцию NSString's size(withAttributes:) или функцию NSAttributedString's size(). Это только частично полезно, потому что они предполагают, что текст - одна строка.

  2. Используйте функцию NSAttributedString's boundingRect(), которая принимает несколько опций рисования, гарантируя, что вы предоставите .usesLineFragmentOrigin для поддержки нескольких строк:

    var textToMeasure = label.attributedText
    
    // Modify the font size in `textToMeasure` as necessary
    
    // Now measure
    let rect = textToMeasure.boundingRect(with: label.bounds, options: [. usesLineFragmentOrigin], context: nil)
    
  3. Используйте TextKit и свой собственный NSLayoutManager:

    var textToMeasure = label.attributedText
    
    // Modify the font size in `textToMeasure` as necessary
    
    // Now measure
    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: CGSize(width: label.bounds.width, height: .greatestFiniteMagnitude))
    let textStorage = NSTextStorage(attributedString: string)
    textStorage.addLayoutManager(layoutManager)
    layoutManager.addTextContainer(textContainer)
    let glyphRange = layoutManager.glyphRange(for: textContainer)
    let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
    
  4. Используйте CoreText, более мощный и низкоуровневый API для размещения текста. Это, вероятно, было бы излишне сложно для этой задачи.

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

Выполняя самое большое измерение Word (а не весь текст), вы не хотите ограничивать параметр ширины, который вы предоставляете для некоторых из вышеуказанных функций определения размера, иначе у системы не будет иного выбора, кроме как разбить одно слово, которое вы дал и вернет неверные результаты для ваших целей. Вам нужно будет заменить аргумент width указанных выше методов на CGFloat.greatestFiniteMagnitude:

  • часть ширины аргумента размера boundingRect().
  • часть ширины аргумента размера NSTextContainer().

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

Для надежного рабочего решения, основанного на вышеупомянутом, см. Мою платформу с открытым исходным кодом AccessibilityKit . Несколько примеров AKLabel в действии:

Example1

Example2

Надеюсь это поможет!

2
Kosta Eleftheriou

В комментариях предусмотрено расширение ObjC, которое вычисляет размер шрифта, необходимый для размещения многострочного текста в UILabel . Он был переписан в Swift (так как это 2016):

//
//  NSString+KBAdditions.Swift
//
//  Created by Alexander Mayatsky on 16/03/16.
//
//  Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
//

import Foundation
import UIKit

protocol NSStringKBAdditions {
    func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat
}

extension NSString : NSStringKBAdditions {
    func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
        var fontSize = font.pointSize
        let minimumFontSize = fontSize * minimumScaleFactor


        var attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: font])
        var height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height

        var newFont = font
        //Reduce font size while too large, break if no height (empty string)
        while (height > size.height && height != 0 && fontSize > minimumFontSize) {
            fontSize--;
            newFont = UIFont(name: font.fontName, size: fontSize)!

            attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: newFont])
            height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
        }

        // Loop through words in string and resize to fit
        for Word in self.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) {
            var width = Word.sizeWithAttributes([NSFontAttributeName:newFont]).width
            while (width > size.width && width != 0 && fontSize > minimumFontSize) {
                fontSize--
                newFont = UIFont(name: font.fontName, size: fontSize)!
                width = Word.sizeWithAttributes([NSFontAttributeName:newFont]).width
            }
        }
        return fontSize;
    }
}

Ссылка на полный код: https://Gist.github.com/amayatsky/e6125a2288cc2e4f1bbf

2
Alexander Mayatsky

Swift 4.2:

//
//  String+Utility.Swift
//
//  Created by Philip Engberg on 29/11/2018.
//  Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
//

import Foundation
import UIKit

extension String {
    func fontSize(with font: UIFont, constrainedTo size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
        var fontSize = font.pointSize
        let minimumFontSize = fontSize * minimumScaleFactor

        var attributedText = NSAttributedString(string: self, attributes: [.font: font])
        var height = attributedText.boundingRect(with: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).size.height

        var newFont = font
        //Reduce font size while too large, break if no height (empty string)
        while height > size.height && height != 0 && fontSize > minimumFontSize {
            fontSize -= 1
            newFont = UIFont(name: font.fontName, size: fontSize)!

            attributedText = NSAttributedString(string: self, attributes: [.font: newFont])
            height = attributedText.boundingRect(with: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).size.height
        }

        // Loop through words in string and resize to fit
        for Word in self.components(separatedBy: NSCharacterSet.whitespacesAndNewlines) {
            var width = Word.size(withAttributes: [.font: newFont]).width
            while width > size.width && width != 0 && fontSize > minimumFontSize {
                fontSize -= 1
                newFont = UIFont(name: font.fontName, size: fontSize)!
                width = Word.size(withAttributes: [.font: newFont]).width
            }
        }
        return fontSize
    }
}
0
Zappel