我有一个多行UILabel,其字体大小我想根据文本长度进行调整。整个文本应该适合标签的框架而不截断它。
不幸的是,根据文档,adjustsFontSizeToFitWidth
属性“仅在numberOfLines
属性设置为1”时才有效。
我尝试使用
确定调整后的字体大小-[NSString (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode]
然后递减字体大小直到它适合。不幸的是,此方法在内部截断文本以适应指定的大小,并返回生成的截断字符串的大小。
答案 0 :(得分:50)
在this question中,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];
编辑:修正了使用newFont
初始化font
的代码。修复了某些情况下的崩溃。
答案 1 :(得分:6)
在某些情况下,如果您知道自己需要多少行(例如“2”),则可能只需要将“换行符”从“自动换行”更改为“截断尾部”:信用:Becky Hansmeyer
答案 2 :(得分:3)
谢谢,有了这个,还有其他人,我做了这个自定义的UILabel,它会尊重最小的字体大小,还有一个额外的选项可以将文本对齐到顶部。
H:
@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 :(得分:2)
注释中提供了一个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
答案 4 :(得分:2)
有关完整的解决方案,请参见答案的底部
要手动测量text
的{{1}} / attributedText
的尺寸,以便使用自己的策略找到合适的字体大小,您可以选择以下几种方法:
使用UILabel
的{{1}}或NSString
的{{1}}函数。这些只是部分有用,因为它们假定文本为一行。
使用size(withAttributes:)
的{{1}}函数,该函数需要一些绘图选项,请确保提供NSAttributedString
以支持多行:
size()
使用TextKit和您自己的NSLayoutManager:
NSAttributedString
使用CoreText,这是一种功能更强大的底层API,用于布置文本。对于此任务,这可能不必要地复杂。
无论您选择使用哪种方式来测量文本,您都可能需要进行两次遍历:第一遍是考虑长单词,不应将其分解成多行,而您需要在其中找到最大的单词完全适合标签范围内最大(〜最长)单词的字体大小。在第二遍中,您可以从第一遍的结果继续向下搜索,以找到所需的甚至更小的字体大小,以适应这次的整个文本。
在进行最大的字词量度(而不是整个文本)时,您不想将提供的width参数限制为上述某些大小调整函数,否则系统别无选择,只能将您给它一个字,并为您的目的返回不正确的结果。您将需要用boundingRect()
替换上述方法的width参数:
.usesLineFragmentOrigin
的size参数的宽度部分。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)
的size参数的宽度部分。您还需要考虑用于搜索的实际算法,因为测量文本是一项昂贵的操作,而线性搜索可能会太慢,具体取决于您的需求。如果您想提高效率,可以改用二进制搜索。
有关基于上述内容的可靠解决方案,请参见我的开源框架AccessibilityKit。实际使用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)
的几个示例:
希望这会有所帮助!
答案 5 :(得分:1)
我想在答案上加上自己的看法:
我认为在其他一些答案上略有改善,因为:
https://gist.github.com/chrisjrex/c571056a4b621f7099bcbd5e179f184f
注意:仅当UILabel具有.numberOfLines = 0
和.lineBreakMode = .byWordWrapping
时才应使用此解决方案
否则,最好使用标准swift库中的.adjustFontSizeForWidth = true
此线程上的其他答案对我提出解决方案非常有帮助,谢谢
答案 6 :(得分:0)
//
// 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
}
}
答案 7 :(得分:0)
在我看来,这个问题现在有了更好的答案
我发现,当您将AdjustsFontSizeToFitWidth设置为true,numberOfLines为0,最小缩放因子(足以使字体根据需要缩小)以及默认的lineBreakMode(byTruncatingTail)设置时,字体大小会在多行标签上自动调整。
PS:我在官方文档中找不到有关此更改的任何信息。我创建了一个问题,以查找有关此主题here
的更多信息