iphone - tipo - ¿Cómo ajustar el tamaño de fuente de la etiqueta para que se ajuste al rectángulo?
margenes en pages (14)
Aquí está la versión de Swift según la respuesta de @NielsCastle, usando la búsqueda binaria
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
}
}
}
}
Uso
label.adjustFontSizeToFitRect(rect)
a menudo solo
label.adjustFontSizeToFitRect(rect.frame)
Sí, hay esto genial myLabel.adjustsFontSizeToFitWidth = YES;
propiedad. Pero tan pronto como la etiqueta tenga dos líneas o más, no cambiará el tamaño del texto a nada. Entonces se trunca con ... si no encaja en el rect.
¿Hay alguna otra forma de hacerlo?
Aquí hay una extensión de Swift para UILabel. Ejecuta un algoritmo de búsqueda binario para cambiar el tamaño de la fuente y los límites de la etiqueta, y está probado para trabajar con iOS 9.
USO: Cambia el tamaño de la fuente para que se ajuste a un tamaño de 100x100 (precisa en 1.0 punto de fuente).
<label>.fitFontForSize(CGSizeMake(100.0, 100.0))
Copie / pegue lo siguiente en su archivo
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()
}
}
NOTA: las restricciones de ancho y altura de la etiqueta no deben establecerse para que esto funcione. Un trabajo fácil sería insertar la etiqueta en una UIView que haya establecido restricciones de tamaño, y luego llamar a la función definida anteriormente (como se hace a continuación).
<label>.fitFontForSize(<superview>.bounds.size)
Como no encontré una solución que funcione que responda a todas mis necesidades utilizando las respuestas anteriores, he creado mis propios componentes con las siguientes características: FittableFontLabel
- Ajuste la fuente para que se ajuste a la altura y el ancho cuando use la etiqueta multilínea
- Ajuste la fuente para que se ajuste al ancho cuando use etiquetas de una sola línea, la etiqueta de altura cambiará de tamaño
- Soporte para
NSAttributedStrings
, así como cadena básica - Ajuste automático del tamaño al cambiar una etiqueta texto / marco
- ...
Si alguno de ustedes es interesante, es una biblioteca rápida disponible usando CocoaPods: https://github.com/tbaranes/FittableFontLabel
Creé Categoría para UILabel en base a la respuesta de @ agarcian. Pero calculo fontSize dependiendo del cuadrado que se necesita en la pantalla para dibujar el texto. Este método no necesita bucles y el cálculo se realiza en una iteración.
Aquí el archivo .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
Y aquí el archivo .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
Esta solución (basada en share ) funciona con el diseño automático y realiza una búsqueda binaria para encontrar el mejor tamaño de fuente.
La única advertencia que he encontrado es que no puede especificar el número de líneas (porque AFAIK no puede decirle a boundingRectWithSize
cuántas líneas desea).
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
Uso
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];
La respuesta de @ agarcian estuvo cerca pero no funcionó para mí, como alguien mencionó en un comentario, siempre regresó 0.
Aquí está mi intento.
¡Aclamaciones!
/**
* 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://.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];;
}
}
La respuesta de Niels me pareció la mejor respuesta para este problema. Sin embargo, tengo una UIView que puede tener 100 etiquetas donde necesito ajustar el texto, por lo que este proceso fue muy ineficiente y pude sentir el impacto en el rendimiento.
Aquí está su código modificado para usar una búsqueda binaria, en lugar de una búsqueda lineal. Ahora funciona de manera muy eficiente.
- (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];
}
El crédito va también a https://gist.github.com/988219
Si alguien está buscando una implementación de MonoTouch / Xamarin.iOS, como lo hice ... aquí va:
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);
}
Es una traducción del código de agarcian de Objective-C a C #, con una pequeña modificación: como el resultado de retorno siempre ha sido 0 ( ver el comentario de borked ), devuelvo el minFontSize calculado, que da como resultado un tamaño de letra correcto.
Si desea asegurarse de que la etiqueta encaje en el rectángulo, tanto en anchura como en altura, puede probar con un tamaño de fuente diferente en la etiqueta para ver si encaja.
Este fragmento comienza en 300 puntos e intenta ajustar la etiqueta en el rectángulo seleccionado reduciendo el tamaño de la fuente.
- (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);
}
Creo que lo anterior explica lo que sucede. Una implementación más rápida podría usar el almacenamiento en caché y la búsqueda binaria de Argarcians de la siguiente manera
+ (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];
}
}
Swift 3 "solución de búsqueda binaria" basada en this respuesta con pequeñas mejoras. La muestra está en el contexto de la subclase 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)
}
}
Espero que ayude a alguien.
Todas estas son soluciones interesantes para el problema original, sin embargo, a todas ellas también les falta algo importante: si confías únicamente en el familyName para obtener la siguiente fuente para probar, estás perdiendo la información de peso y posiblemente atributos más avanzados como pequeños gorras, estilo de figura, etc.
Un mejor enfoque es en lugar de pasar el nombre de la fuente y hacer [UIFont fontWithName:someFontName size:someFontSize]
, pasar los objetos UIFontDescriptor
largo y luego hacer [UIFont fontWithDescriptor:someFontDescriptor size:someFontSize]
.
Todas las búsquedas binarias son buenas, pero detienen la recursión mediante el uso de verificaciones de marcos no lógicamente. Compruebe con más precisión el tamaño de letra, ya que UIFont admite el tamaño de letra flotante y esta fuente es más adecuada. Además, usa el estilo de párrafo de la etiqueta para calcular el tamaño de manera exacta.
Si alguien es interesante, puedes buscar el siguiente código:
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];
}
Trabajo de código de Niels Castle encontrar.
Aquí está la misma idea con una implementación diferente.
Mi solución es más precisa pero también requiere mucha más CPU.
Agregue esta función a una clase que hereda 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);
}
myLabel.numberOfLines = 10
también myLabel.numberOfLines = 10
o al número máximo de líneas que desee.