¿Cómo ajustar el tamaño de fuente de la etiqueta para que se ajuste al rectángulo?

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?

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]; } } 

La respuesta de Niels me pareció la mejor respuesta para este problema. Sin embargo, tengo una UIView que puede tener 100 tags 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

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) 

Esta solución (basada en esta respuesta ) 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  @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]; 

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.

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 100×100 (precisa en 1.0 punto de fuente).

  

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).

  

myLabel.numberOfLines = 10 también myLabel.numberOfLines = 10 o al número máximo de líneas que desee.

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] .

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); } 

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  @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 

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]; } 

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 tags 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

Swift 3 “solución de búsqueda binaria” basada en esta 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.

La respuesta de @ agarcian estuvo cerca pero no funcionó para mí, como alguien mencionó en un comentario, siempre regresó 0.

Aquí está mi bash.

¡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://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];; } }