web-dev-qa-db-ja.com

adjustsFontSizeToFitWidthを使用したマルチラインUILabel

テキストの長さに応じてフォントサイズを調整する複数行のUILabelがあります。テキスト全体が切り捨てられることなく、ラベルのフレームに収まる必要があります。

残念ながら、ドキュメントによると、adjustsFontSizeToFitWidthプロパティは「numberOfLinesプロパティが1に設定されている場合のみ有効です」。

私は使用して調整されたフォントサイズを決定しようとしました

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

そして、それが収まるまでフォントサイズを減らします。残念ながら、このメソッドは指定されたサイズに収まるようにテキストを内部的に切り捨て、結果の切り捨てられた文字列のサイズを返します。

43
Ortwin Gentz

この質問 では、0x90がソリューションを提供します-少し醜いですが-私が望むことをします。具体的には、1つのWordが初期フォントサイズの幅に合わない状況を正しく処理します。 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 [〜#〜]newFontfontで初期化するようにコードを修正しました。特定の状況下でのクラッシュを修正しました。

50
Ortwin Gentz

必要な行数がわかっている場合(「2」など)、「改行」を「ワードラップ」から「切り捨て」に変更するだけで十分な場合もあります。クレジット: Becky Hansmeyer =

6
weienw

おかげで、それと、他の誰かから私がこのカスタムUILabelをやったことで、最小フォントサイズが尊重され、テキストを上に揃えるためのボーナスオプションがあります。

h:

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

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

m:

#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

完全に機能する解決策については、私の答えの下部を参照してください????

独自の戦略を使用して適切なフォントサイズを見つけるために、textattributedText/UILabelの寸法を手動で測定するには、いくつかのオプションがあります。

  1. NSStringsize(withAttributes:)またはNSAttributedStringsize()関数を使用します。これらはテキストが1行であると想定しているため、部分的にしか役に立ちません。

  2. いくつかの描画オプションを使用するNSAttributedStringboundingRect()関数を使用し、複数の行をサポートするために_.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. テキストのレイアウトに、より強力で低レベルのAPIであるCoreTextを使用します。これは、おそらくこのタスクでは不必要に複雑になるでしょう。

テキストの測定に何を使用するかに関係なく、おそらく2つのパスを実行する必要があります。最初のパスは、複数の行に分割されるべきではない長い単語を考慮するためのもので、最大のフォントサイズを見つける必要があります。ラベルの境界内で最大(〜最長)のWordに完全に適合します。 2番目のパスでは、最初のパスの結果から下に向かって検索を続け、今回はテキスト全体に合わせるために必要なさらに小さいフォントサイズを見つけることができます。

(テキスト全体ではなく)最大のWord測定を行う場合、上記のサイズ変更関数のいくつかに提供する幅パラメーターを制限する必要はありません。そうしないと、システムは1つのWordを分割するしかありません。それを与え、あなたの目的のために間違った結果を返します。上記のメソッドの幅引数を_CGFloat.greatestFiniteMagnitude_に置き換える必要があります。

  • boundingRect()のサイズ引数の幅部分。
  • NSTextContainer()のサイズ引数の幅部分。

また、テキストの測定はコストのかかる操作であり、線形検索はニーズによっては非常に遅くなる可能性があるため、検索に使用する実際のアルゴリズムを検討する必要もあります。より効率的になりたい場合は、代わりにバイナリ検索を適用できます。 ????

上記に基づく堅牢な作業ソリューションについては、私のオープンソースフレームワーク AccessibilityKit を参照してください。実際のAKLabelの例:

Example1

Example2

お役に立てれば!

2

コメントには、複数行のテキストをUILabelに合わせるために必要なフォントサイズを計算するObjC拡張があります。 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

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

この質問に対するより良い答えがあるように私には思えます

AdjustsFontSizeToFitWidth true、numberOfLines 0、minimumScaleFactor、必要に応じてフォントを縮小するのに十分な最小の縮尺、およびデフォルトのlineBreakMode(byTruncatingTail)を設定すると、フォントサイズが複数行ラベルで自動的に調整されることを発見しました

PS:この変更については公式ドキュメントでは何も見つかりません。このトピックの詳細を確認するために質問を作成しました ここ

0
Daniele Cux