是的,这是一个很酷的myLabel.adjustsFontSizeToFitWidth = YES;
财产。但是只要标签有两行或更多行,它就不会将文本大小调整为任何内容。因此,如果它不适合矩形,它就会被截断。
还有其他办法吗?
答案 0 :(得分:49)
如果你想确保标签适合宽度和高度的矩形,你可以在标签上尝试不同的字体大小,看看是否适合。
此代码段以300 pt开始,并尝试通过缩小字体大小来使标签符合目标矩形。
- (void) sizeLabel: (UILabel *) label toRect: (CGRect) labelRect {
// Set the frame of the label to the targeted rectangle
label.frame = labelRect;
// Try all font sizes from largest to smallest font size
int fontSize = 300;
int minFontSize = 5;
// Fit label width wize
CGSize constraintSize = CGSizeMake(label.frame.size.width, MAXFLOAT);
do {
// Set current font size
label.font = [UIFont fontWithName:label.font.fontName size:fontSize];
// Find label size for current font size
CGRect textRect = [[label text] boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: label.font}
context:nil];
CGSize labelSize = textRect.size;
// Done, if created label is within target size
if( labelSize.height <= label.frame.size.height )
break;
// Decrease the font size and try again
fontSize -= 2;
} while (fontSize > minFontSize);
}
我认为上面解释了发生了什么。更快的实现可以使用缓存和argarcians二进制搜索,如下所示
+ (CGFloat) fontSizeForString: (NSString*) s inRect: (CGRect) labelRect {
// Cache repeat queries
static NSMutableDictionary* mutableDict = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mutableDict = [NSMutableDictionary dictionary];
});
NSString* key = [NSString stringWithFormat:@"%@_%d_%d", s, (int) labelRect.size.width, (int) labelRect.size.height];
NSNumber* value = [mutableDict objectForKey:key];
if (value)
return value.doubleValue;
// Set the frame of the label to the targeted rectangle
UILabel* label = [[UILabel alloc] init];
label.text = s;
label.frame = labelRect;
// Hopefully between 5 and 300
CGFloat theSize = (CGFloat) [self binarySearchForFontSizeForLabel:label withMinFontSize:5 withMaxFontSize:300 withSize:label.frame.size];
[mutableDict setObject:@(theSize) forKey:key];
return theSize;
}
+ (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
// If the sizes are incorrect, return 0, or error, or an assertion.
if (maxFontSize < minFontSize) {
return maxFontSize;
}
// Find the middle
NSInteger fontSize = (minFontSize + maxFontSize) / 2;
// Create the font
UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
// Create a constraint size with max height
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
// Find label size for current font size
CGRect rect = [label.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
CGSize labelSize = rect.size;
// EDIT: The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
return fontSize;
} else if (labelSize.height > size.height || labelSize.width > size.width) {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
} else {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
}
}
答案 1 :(得分:31)
我发现Niels的回答是这个问题的最佳答案。但是,我有一个UIView,它可以有100个标签,我需要适应文本,所以这个过程非常低效,我可以感受到性能的打击。
这是他的代码被修改为使用二进制搜索而不是线性搜索。现在它非常有效。
- (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
// If the sizes are incorrect, return 0, or error, or an assertion.
if (maxFontSize < minFontSize) {
return 0;
}
// Find the middle
NSInteger fontSize = (minFontSize + maxFontSize) / 2;
// Create the font
UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
// Create a constraint size with max height
CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
// Find label size for current font size
CGRect rect = [label.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
CGSize labelSize = rect.size;
// EDIT: The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
return fontSize;
} else if (labelSize.height > size.height || labelSize.width > size.width) {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
} else {
return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
}
}
- (void)sizeBinaryLabel:(UILabel *)label toRect:(CGRect)labelRect {
// Set the frame of the label to the targeted rectangle
label.frame = labelRect;
// Try all font sizes from largest to smallest font
int maxFontSize = 300;
int minFontSize = 5;
NSInteger size = [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:maxFontSize withSize:label.frame.size];
label.font = [UIFont fontWithName:label.font.fontName size:size];
}
答案 2 :(得分:8)
这是Swift版本根据@NielsCastle的回答,使用二进制搜索
extension UILabel{
func adjustFontSizeToFitRect(rect : CGRect){
if text == nil{
return
}
frame = rect
let maxFontSize: CGFloat = 100.0
let minFontSize: CGFloat = 5.0
var q = Int(maxFontSize)
var p = Int(minFontSize)
let constraintSize = CGSize(width: rect.width, height: CGFloat.max)
while(p <= q){
let currentSize = (p + q) / 2
font = font.fontWithSize( CGFloat(currentSize) )
let text = NSAttributedString(string: self.text!, attributes: [NSFontAttributeName:font])
let textRect = text.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, context: nil)
let labelSize = textRect.size
if labelSize.height < frame.height && labelSize.height >= frame.height-10 && labelSize.width < frame.width && labelSize.width >= frame.width-10 {
break
}else if labelSize.height > frame.height || labelSize.width > frame.width{
q = currentSize - 1
}else{
p = currentSize + 1
}
}
}
}
用法
label.adjustFontSizeToFitRect(rect)
通常只是
label.adjustFontSizeToFitRect(rect.frame)
答案 3 :(得分:5)
此解决方案(基于this answer)与自动布局配合使用,并执行二进制搜索以找到最佳字体大小。
我发现的唯一警告是你无法指定行数(因为AFAIK你不能告诉boundingRectWithSize
你想要多少行。)
<强> AdjustableLabel.h 强>
#import <UIKit/UIKit.h>
@interface AdjustableLabel : UILabel
/**
If set to YES, font size will be automatically adjusted to frame.
Note: numberOfLines can't be specified so it will be set to 0.
*/
@property(nonatomic) BOOL adjustsFontSizeToFitFrame;
@end
<强> AdjustableLabel.m 强>
#import "AdjustableLabel.h"
@interface AdjustableLabel ()
@property(nonatomic) BOOL fontSizeAdjusted;
@end
// The size found S satisfies: S fits in the frame and and S+DELTA doesn't.
#define DELTA 0.5
@implementation AdjustableLabel
- (void)setAdjustsFontSizeToFitFrame:(BOOL)adjustsFontSizeToFitFrame
{
_adjustsFontSizeToFitFrame = adjustsFontSizeToFitFrame;
if (adjustsFontSizeToFitFrame) {
self.numberOfLines = 0; // because boundingRectWithSize works like this was 0 anyway
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.adjustsFontSizeToFitFrame && !self.fontSizeAdjusted)
{
self.fontSizeAdjusted = YES; // to avoid recursion, because adjustFontSizeToFrame will trigger this method again
[self adjustFontSizeToFrame];
}
}
- (void) adjustFontSizeToFrame
{
UILabel* label = self;
if (label.text.length == 0) return;
// Necessary or single-char texts won't be correctly adjusted
BOOL checkWidth = label.text.length == 1;
CGSize labelSize = label.frame.size;
// Fit label width-wise
CGSize constraintSize = CGSizeMake(checkWidth ? MAXFLOAT : labelSize.width, MAXFLOAT);
// Try all font sizes from largest to smallest font size
CGFloat maxFontSize = 300;
CGFloat minFontSize = 5;
NSString* text = label.text;
UIFont* font = label.font;
while (true)
{
// Binary search between min and max
CGFloat fontSize = (maxFontSize + minFontSize) / 2;
// Exit if approached minFontSize enough
if (fontSize - minFontSize < DELTA/2) {
font = [UIFont fontWithName:font.fontName size:minFontSize];
break; // Exit because we reached the biggest font size that fits
} else {
font = [UIFont fontWithName:font.fontName size:fontSize];
}
// Find label size for current font size
CGRect rect = [text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
// Now we discard a half
if( rect.size.height <= labelSize.height && (!checkWidth || rect.size.width <= labelSize.width) ) {
minFontSize = fontSize; // the best size is in the bigger half
} else {
maxFontSize = fontSize; // the best size is in the smaller half
}
}
label.font = font;
}
@end
<强>用法强>
AdjustableLabel* label = [[AdjustableLabel alloc] init];
label.adjustsFontSizeToFitFrame = YES;
// In case you change the font, the size you set doesn't matter
label.font = [UIFont fontWithName:@"OpenSans-Light" size:20];
答案 4 :(得分:3)
这是UILabel的Swift扩展。它运行二进制搜索算法来调整标签的字体和边界大小,并经过测试,适用于iOS 12。
USAGE:调整字体大小以适应100x100的大小(准确地在1.0字体点内)并将其对齐到顶部。
let adjustedSize = <label>.fitFontForSize(CGSizeMake(100, 100))
<label>.frame = CGRect(x: 0, y: 0, width: 100, height: adjustedSize.height)
将以下内容复制/粘贴到您的文件中:
extension UILabel {
@discardableResult func fitFontForSize(_ constrainedSize: CGSize,
maxFontSize: CGFloat = 100,
minFontSize: CGFloat = 5,
accuracy: CGFloat = 1) -> CGSize {
assert(maxFontSize > minFontSize)
var minFontSize = minFontSize
var maxFontSize = maxFontSize
var fittingSize = constrainedSize
while maxFontSize - minFontSize > accuracy {
let midFontSize: CGFloat = ((minFontSize + maxFontSize) / 2)
font = font.withSize(midFontSize)
fittingSize = sizeThatFits(constrainedSize)
if fittingSize.height <= constrainedSize.height
&& fittingSize.width <= constrainedSize.width {
minFontSize = midFontSize
} else {
maxFontSize = midFontSize
}
}
return fittingSize
}
}
此功能不会更改标签大小,只会影响font
属性。您可以使用返回的大小值来调整标签的布局。
答案 5 :(得分:2)
如果有人正在寻找MonoTouch / Xamarin.iOS实现,就像我一样...在这里你去:
private int BinarySearchForFontSizeForText(NSString text, int minFontSize, int maxFontSize, SizeF size)
{
if (maxFontSize < minFontSize)
return minFontSize;
int fontSize = (minFontSize + maxFontSize) / 2;
UIFont font = UIFont.BoldSystemFontOfSize(fontSize);
var constraintSize = new SizeF(size.Width, float.MaxValue);
SizeF labelSize = text.StringSize(font, constraintSize, UILineBreakMode.WordWrap);
if (labelSize.Height >= size.Height + 10 && labelSize.Width >= size.Width + 10 && labelSize.Height <= size.Height && labelSize.Width <= size.Width)
return fontSize;
else if (labelSize.Height > size.Height || labelSize.Width > size.Width)
return BinarySearchForFontSizeForText(text, minFontSize, fontSize - 1, size);
else
return BinarySearchForFontSizeForText(text, fontSize + 1, maxFontSize, size);
}
private void SizeLabelToRect(UILabel label, RectangleF labelRect)
{
label.Frame = labelRect;
int maxFontSize = 300;
int minFontSize = 5;
int size = BinarySearchForFontSizeForText(new NSString(label.Text), minFontSize, maxFontSize, label.Frame.Size);
label.Font = UIFont.SystemFontOfSize(size);
}
这是agarcian的代码从Objective-C到C#的翻译,只有一个小的修改:因为返回的结果一直是0(see the comment of borked)我返回计算的minFontSize,这会得到正确的字体大小
答案 6 :(得分:1)
同时设置myLabel.numberOfLines = 10
或任意所需的最大行数。
答案 7 :(得分:1)
所有这些都是原始问题的有趣解决方案,但是所有这些都缺少一个重要的事情:如果你完全依赖familyName来测试 next 字体,那么你就可以了。重新丢失重量信息以及可能更高级的属性,如小型帽子,数字样式等。
更好的方法是不要传递字体名称并执行[UIFont fontWithName:someFontName size:someFontSize]
,而是传递UIFontDescriptor
个对象然后再做
[UIFont fontWithDescriptor:someFontDescriptor size:someFontSize]
。
答案 8 :(得分:1)
由于我找不到使用上述答案满足我所有需求的工作解决方案,因此我创建了自己的组件,提供以下功能:FittableFontLabel
NSAttributedStrings
以及基本字符串如果你们中的任何人都很有趣,那么它就是一个完整的swift库 CocoaPods:https://github.com/tbaranes/FittableFontLabel
答案 9 :(得分:0)
Niels Castle代码工作查找。
以下是不同实施的相同想法
我的解决方案更精确,但CPU密集度更高。
将此函数添加到继承UILabel的类中。
-(void)fitCurrentFrame{
CGSize iHave = self.frame.size;
BOOL isContained = NO;
do{
CGSize iWant = [self.text sizeWithFont:self.font];
if(iWant.width > iHave.width || iWant.height > iHave.height){
self.font = [UIFont fontWithName:self.font.fontName size:self.font.pointSize - 0.1];
isContained = NO;
}else{
isContained = YES;
}
}while (isContained == NO);
}
答案 10 :(得分:0)
我根据@ agarcian的回答为UILabel创建了Category。但我根据屏幕上绘制文本所需的平方来计算fontSize。这种方法不需要循环,计算通过一次迭代完成。
这里是.h文件:
// UILabel+Extended.h
// Created by Firuz on 16/08/14.
// Copyright (c) 2014. All rights reserved.
#import <UIKit/UIKit.h>
@interface UILabel (Extended)
/** This method calculate the optimal font size for current number of lines in UILable. Mus be called after drawing UILabel view */
- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize;
@end
这里是.m文件:
// UILabel+Extended.m
// Created by Firuz on 16/08/14.
// Copyright (c) 2014. All rights reserved.
#import "UILabel+Extended.h"
@implementation UILabel (Extended)
- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize
{
if (maxFontSize < minFontSize) {
return 0;
}
UIFont *font = [UIFont fontWithName:self.font.fontName size:maxFontSize];
CGFloat lineHeight = [font lineHeight];
CGSize constraintSize = CGSizeMake(MAXFLOAT, lineHeight);
CGRect rect = [self.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName : font}
context:nil];
CGFloat labelSqr = self.frame.size.width * self.frame.size.height;
CGFloat stringSqr = rect.size.width/self.frame.size.width * (lineHeight + font.pointSize) * self.frame.size.width;
CGFloat multiplyer = labelSqr/stringSqr;
if (multiplyer < 1) {
if (minFontSize < maxFontSize*multiplyer) {
return maxFontSize * multiplyer;
} else {
return minFontSize;
}
}
return maxFontSize;
}
@end
答案 11 :(得分:0)
所有二进制搜索都很好,但使用帧检查停止递归并不是那么逻辑。更好的检查字体大小,导致UIFont支持浮动大小,这种字体更合适。 另外,使用标签段落样式来精确计算尺寸。
如果有人有兴趣,你可以看下面的代码:
static UIFont * ___suitableFontInRangePrivate(const CGSize labelSize,
NSParagraphStyle * paragraphStyle,
NSString * fontName,
NSString * text,
const CGFloat minSize,
const CGFloat maxSize)
{
// Font size in range, middle size between max & min.
const CGFloat currentSize = minSize + ((maxSize - minSize) / 2);
// Font with middle size.
UIFont * currentFont = [UIFont fontWithName:fontName size:currentSize];
// Calculate text height.
const CGFloat textHeight = [text boundingRectWithSize:CGSizeMake(labelSize.width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{ NSFontAttributeName : currentFont, NSParagraphStyleAttributeName : paragraphStyle }
context:nil].size.height;
CGFloat min, max;
if (textHeight > labelSize.height)
{
// Take left range part.
min = minSize;
max = currentSize;
}
else
{
// Take right range part.
min = currentSize;
max = maxSize;
}
// If font size in int range [0.0; 2.0] - got it, othervice continue search.
return ((max - min) <= 2.0) ? currentFont : ___suitableFontInRangePrivate(labelSize, paragraphStyle, fontName, text, min, max);
}
void UILabelAdjustsFontSizeToFrame(UILabel * label)
{
if (!label) return;
NSString * text = [label text];
__block NSParagraphStyle * style = nil;
[[label attributedText] enumerateAttributesInRange:NSMakeRange(0, [text length])
options:(NSAttributedStringEnumerationOptions)0
usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop){
id paragraphStyle = [attrs objectForKey:@"NSParagraphStyle"];
if (paragraphStyle) style = [paragraphStyle retain];
}];
if (!style)
{
NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init];
if (paragraphStyle)
{
[paragraphStyle setLineBreakMode:[label lineBreakMode]];
[paragraphStyle setAlignment:[label textAlignment]];
}
style = paragraphStyle;
}
UIFont * suitableFont = ___suitableFontInRangePrivate([label frame].size, style, [[label font] fontName], text, 0, 500);
[label setFont:suitableFont];
[style release];
}
答案 12 :(得分:0)
Swift 3“二进制搜索解决方案”基于this回答,略有改进。示例位于UITextView
子类的上下文中:
func binarySearchOptimalFontSize(min: Int, max: Int) -> Int {
let middleSize = (min + max) / 2
if min > max {
return middleSize
}
let middleFont = UIFont(name: font!.fontName, size: CGFloat(middleSize))!
let attributes = [NSFontAttributeName : middleFont]
let attributedString = NSAttributedString(string: text, attributes: attributes)
let size = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
let textSize = attributedString.boundingRect(with: size, options: options, context: nil)
if textSize.size.equalTo(bounds.size) {
return middleSize
} else if (textSize.height > bounds.size.height || textSize.width > bounds.size.width) {
return binarySearchOptimalFontSize(min: min, max: middleSize - 1)
} else {
return binarySearchOptimalFontSize(min: middleSize + 1, max: max)
}
}
我希望能帮助别人。
答案 13 :(得分:0)
@ agarcian的答案很接近,但对我来说并不是很有效,正如其他人在评论中提到的那样,它总是返回0。
这是我的尝试。
干杯!
/**
* Returns the font size required in order to fit the specified text in the specified area.
* NB! When drawing, be sure to pass in the same options that we pass to boundingRectWithSize:options:attributes:context:
* Heavily modified form of: http://stackoverflow.com/a/14662750/1027452
*/
+(NSInteger)fontSizeForText:(NSString *)text withFont:(UIFont *)font inArea:(CGSize)areaSize minFontSize:(NSInteger)minFontSize maxFontSize:(NSInteger)maxFontSize
{
// If the sizes are incorrect, return 0, or error, or an assertion.
if (maxFontSize < minFontSize) {
return 0;
}
// Find the middle
NSInteger fontSize = (minFontSize + maxFontSize) / 2;
// Create the font
UIFont *f = [UIFont fontWithName:font.fontName size:fontSize];
// Create a constraint size with max height
CGSize constraintSize = CGSizeMake(areaSize.width, MAXFLOAT);
// Find label size for current font size
CGRect rect = [text boundingRectWithSize:constraintSize
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:@{NSFontAttributeName : f}
context:nil];
CGSize labelSize = rect.size;
if (labelSize.height <= areaSize.height && labelSize.width <= areaSize.width )
{
return fontSize;
}
else if (labelSize.height > areaSize.height || labelSize.width > areaSize.width)
{
return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize maxFontSize:maxFontSize -1];;
}
else
{
return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize+1 maxFontSize:maxFontSize];;
}
}