Как отрегулировать размер шрифта метки для соответствия прямоугольнику?
Да, вот это классное свойство myLabel.adjustsFontSizeToFitWidth = YES;
. Но как только ярлык имеет две строки или больше, он не будет изменять размер текста на что угодно. Поэтому он просто усекает с... если он не вписывается в прямоугольник.
Есть ли другой способ сделать это?
Ответы
Ответ 1
Если вы хотите, чтобы метка соответствовала прямоугольнику как по ширине, так и по высоте, вы можете попробовать другой размер шрифта на ярлыке, чтобы увидеть, будет ли он соответствовать.
Этот фрагмент начинается с 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);
}
Ответ 2
Я нашел ответ Нильса лучшим ответом на этот вопрос. Тем не менее, у меня есть 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];
}
Кредит также относится к https://gist.github.com/988219
Ответ 3
Здесь версия 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)
Ответ 4
Это решение (основанное на этом ответе) работает с авто-макетом и выполняет бинарный поиск, чтобы найти лучший размер шрифта.
Единственное предостережение, которое я обнаружил, заключается в том, что вы не можете указать количество строк (потому что 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];
Ответ 5
Если кто-то ищет реализацию 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);
}
Это перевод агарового кода от Objective-C до С# с небольшой модификацией: поскольку возвращаемый результат всегда был 0 (см. комментарий borked) Я возвращаю вычисляемый minFontSize, что приводит к правильному размеру шрифта.
Ответ 6
Здесь скоростное расширение для UILabel. Он выполняет алгоритм бинарного поиска для изменения размера шрифта и границ метки и проверяется на работу в iOS 9.
ИСПОЛЬЗОВАНИЕ: Изменяет размер шрифта до размера 100x100 (точнее в пределах 1.0 точки шрифта).
<label>.fitFontForSize(CGSizeMake(100.0, 100.0))
Скопировать/Вставить в свой файл
следующее:
extension UILabel {
func fitFontForSize(constrainedSize : CGSize, var maxFontSize : CGFloat = 300.0, var minFontSize : CGFloat = 5.0, accuracy : CGFloat = 1.0) {
assert(maxFontSize > minFontSize)
while maxFontSize - minFontSize > accuracy {
let midFontSize : CGFloat = ((minFontSize + maxFontSize) / 2)
font = font.fontWithSize(midFontSize)
sizeToFit()
let checkSize : CGSize = bounds.size
if checkSize.height < constrainedSize.height && checkSize.width < constrainedSize.width {
minFontSize = midFontSize
} else {
maxFontSize = midFontSize
}
}
font = font.fontWithSize(minFontSize)
sizeToFit()
}
}
Примечание:
Ограничения ширины и высоты метки не должны быть установлены, чтобы это работало. Легкая работа вокруг заключается в том, чтобы вставить ярлык в UIView, который установил ограничения по размеру, а затем вызвать функцию, определенную выше (как показано ниже).
<label>.fitFontForSize(<superview>.bounds.size)
Ответ 7
Также установите myLabel.numberOfLines = 10
или любое максимальное количество строк, которое вы хотите.
Ответ 8
Все это интересные решения исходной проблемы, однако все они также пропускают важную вещь: если вы только полагаетесь на familyName, чтобы получить следующий шрифт для тестирования, вы теряете информацию о весе и, возможно, более продвинутые атрибуты, такие как маленькие шапки, стиль фигуры и т.д.
Лучше подойти вместо того, чтобы передавать имя шрифта и делать [UIFont fontWithName:someFontName size:someFontSize]
, передавая объекты UIFontDescriptor
, а затем
[UIFont fontWithDescriptor:someFontDescriptor size:someFontSize]
.
Ответ 9
Поиск работы замка Нильса.
Вот та же идея с другой реализацией.
Мое решение является более точным, но также намного более интенсивным в работе процессора.
Добавьте эту функцию в класс, который наследует 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
Я создал категорию для UILabel на основе ответа @agarcian. Но я вычисляю 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
Все двоичные поиски хороши, но остановка рекурсии с использованием проверки кадров не так логически. Более красиво проверьте размер шрифта, потому что 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
Поскольку я не нашел рабочего решения, отвечающего всем моим потребностям, используя приведенные выше ответы, я создал свои собственные компоненты, предоставляющие следующие функции: FittableFontLabel
- Отрегулируйте шрифт в соответствии с высотой и шириной при использовании многострочной метки
- Отрегулируйте шрифт в соответствии с шириной при использовании метки одной строки, метка высоты изменит ее размер
- Поддержка
NSAttributedStrings
, а также базовая строка
- Автоматическая настройка размера при изменении текста/рамки метки
- ...
Если какой-либо из вас интересен, это полная библиотека быстрого доступа, использующая
CocoaPods: https://github.com/tbaranes/FittableFontLabel
Ответ 13
Swift 3 "решение для двоичного поиска" на основе этого ответ с небольшими улучшениями. Образец находится в контексте подкласса 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)
}
}
Я надеюсь, что это поможет кому-то.
Ответ 14
Ответ на @ararcian был близок, но для меня это не совсем сработало, так как кто-то еще упомянул в комментарии, он всегда возвращал 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];;
}
}