Многострочная UILabel с AdjusttsFontSizeToFitWidth - PullRequest
42 голосов
/ 08 декабря 2010

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

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

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

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

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

Ответы [ 6 ]

50 голосов
/ 08 декабря 2010

В этот вопрос , 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. Исправляет сбой при определенных обстоятельствах.

6 голосов
/ 05 ноября 2015

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

3 голосов
/ 31 августа 2011

Спасибо, и еще немного от кого-то другого я сделал эту пользовательскую 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

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

2 голосов
/ 18 сентября 2018

Полностью рабочее решение см. В нижней части моего ответа 100

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

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

  2. Используйте функцию NSAttributedString 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 для размещения текста. Это, вероятно, было бы излишне сложно для этой задачи.

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

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

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

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

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

Example1

Example2

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

2 голосов
/ 22 марта 2016

В комментариях предусмотрено расширение 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

0 голосов
/ 21 ноября 2018

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