テキストの長さに応じてフォントサイズを調整する複数行のUILabelがあります。テキスト全体が切り捨てられることなく、ラベルのフレームに収まる必要があります。
残念ながら、ドキュメントによると、adjustsFontSizeToFitWidth
プロパティは「numberOfLines
プロパティが1に設定されている場合のみ有効です」。
私は使用して調整されたフォントサイズを決定しようとしました
-[NSString (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode]
そして、それが収まるまでフォントサイズを減らします。残念ながら、このメソッドは指定されたサイズに収まるようにテキストを内部的に切り捨て、結果の切り捨てられた文字列のサイズを返します。
この質問 では、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 [〜#〜]:newFont
をfont
で初期化するようにコードを修正しました。特定の状況下でのクラッシュを修正しました。
必要な行数がわかっている場合(「2」など)、「改行」を「ワードラップ」から「切り捨て」に変更するだけで十分な場合もあります。クレジット: Becky Hansmeyer =
おかげで、それと、他の誰かから私がこのカスタム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
お役に立てば幸いです。
完全に機能する解決策については、私の答えの下部を参照してください????
独自の戦略を使用して適切なフォントサイズを見つけるために、text
のattributedText
/UILabel
の寸法を手動で測定するには、いくつかのオプションがあります。
NSString
のsize(withAttributes:)
またはNSAttributedString
のsize()
関数を使用します。これらはテキストが1行であると想定しているため、部分的にしか役に立ちません。
いくつかの描画オプションを使用する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)
_
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)
_
テキストのレイアウトに、より強力で低レベルのAPIであるCoreTextを使用します。これは、おそらくこのタスクでは不必要に複雑になるでしょう。
テキストの測定に何を使用するかに関係なく、おそらく2つのパスを実行する必要があります。最初のパスは、複数の行に分割されるべきではない長い単語を考慮するためのもので、最大のフォントサイズを見つける必要があります。ラベルの境界内で最大(〜最長)のWordに完全に適合します。 2番目のパスでは、最初のパスの結果から下に向かって検索を続け、今回はテキスト全体に合わせるために必要なさらに小さいフォントサイズを見つけることができます。
(テキスト全体ではなく)最大のWord測定を行う場合、上記のサイズ変更関数のいくつかに提供する幅パラメーターを制限する必要はありません。そうしないと、システムは1つのWordを分割するしかありません。それを与え、あなたの目的のために間違った結果を返します。上記のメソッドの幅引数を_CGFloat.greatestFiniteMagnitude
_に置き換える必要があります。
boundingRect()
のサイズ引数の幅部分。NSTextContainer()
のサイズ引数の幅部分。また、テキストの測定はコストのかかる操作であり、線形検索はニーズによっては非常に遅くなる可能性があるため、検索に使用する実際のアルゴリズムを検討する必要もあります。より効率的になりたい場合は、代わりにバイナリ検索を適用できます。 ????
上記に基づく堅牢な作業ソリューションについては、私のオープンソースフレームワーク AccessibilityKit を参照してください。実際のAKLabel
の例:
お役に立てれば!
コメントには、複数行のテキストを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
//
// 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
}
}
この質問に対するより良い答えがあるように私には思えます
AdjustsFontSizeToFitWidth true、numberOfLines 0、minimumScaleFactor、必要に応じてフォントを縮小するのに十分な最小の縮尺、およびデフォルトのlineBreakMode(byTruncatingTail)を設定すると、フォントサイズが複数行ラベルで自動的に調整されることを発見しました
PS:この変更については公式ドキュメントでは何も見つかりません。このトピックの詳細を確認するために質問を作成しました ここ