2010-12-08 15 views
39

Tengo un UILabel de líneas múltiples cuyo tamaño de fuente me gustaría ajustar dependiendo de la longitud del texto. El texto completo debe caber en el marco de la etiqueta sin truncarlo.UILabel multilínea con ajustesFontSizeToFitWidth

Desafortunadamente, de acuerdo con la documentación, la propiedad adjustsFontSizeToFitWidth "es efectiva solo cuando la propiedad numberOfLines está configurada en 1".

I tratado de determinar el tamaño de fuente ajustado utilizando

-[NSString (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode] 

y luego disminuyendo el tamaño de la fuente hasta que se ajuste. Desafortunadamente, este método trunca internamente el texto para que se ajuste al tamaño especificado y devuelve el tamaño de la cadena truncada resultante.

Respuesta

48

En this question, 0x90 proporciona una solución que, aunque un poco fea, hace lo que quiero. Específicamente, trata correctamente la situación de que una sola palabra no se ajusta al ancho en el tamaño de fuente inicial. He modificado ligeramente el código para que funcione como una categoría en 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; 
} 

para usarlo con un UILabel:

CGFloat fontSize = [label.text fontSizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:label.frame.size]; 
    label.font = [UIFont boldSystemFontOfSize:fontSize]; 

EDITAR: Se ha corregido el código para inicializar newFont con font . Soluciona un bloqueo en ciertas circunstancias.

+1

que he encontrado más exacto utilizar '[componentsSeparatedByCharactersInSet auto: [NSCharacterSet whitespaceAndNewlineCharacterSet]] en lugar de' simplemente dividiendo por el espacio. – Jilouc

+0

Jilouc, gracias, buen consejo. He actualizado el código. –

+5

Esto es genial. Agregué soporte para el tamaño mínimo de fuente y una categoría en 'UILabel' por conveniencia y [lo cargué como una esencia] (https://gist.github.com/2766074) para cualquiera que esté interesado. – kevboh

3

Gracias, con eso y un poco más de otra persona que hice esta UILabel personalizada, que respetará el tamaño mínimo de fuente y hay una opción de bonificación para alinear el texto a la parte superior.

h:

@interface EPCLabel : UILabel { 
    float originalPointSize; 
    CGSize originalSize; 
} 

@property (nonatomic, readwrite) BOOL alignTextOnTop; 
@end 

m:

#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 

espero que ayude.

+0

Brillante !! Gracias. – Smikey

+0

Sin embargo, con todos estos enfoques, si su texto no cabe en el número especificado de líneas en el tamaño de letra inicial (ahora máximo), la llamada constrainedToSize truncará felizmente el texto en función de su salto de línea, para ajustarse al anchura. por lo tanto, terminas encogiéndote solo para ajustar lo que queda, en lugar de lo que creo que realmente quieres, que es "reducir para ajustar todo este texto en n-número de líneas" –

4

El código proporcionado en this blog post también resuelve este problema. Sin embargo, descubrí que recorrer todos los tamaños de fuente posibles era demasiado lento si necesita calcular los tamaños de fuente para muchas etiquetas a la vez, así que modifiqué un poco el código y ahora se ejecuta mucho más rápido.

El único problema con mi solución, hasta donde yo sé, es que no tiene en cuenta la situación en la que una sola palabra no se ajusta al ancho en el tamaño inicial de la fuente, como señaló Ortwin.

Si está buscando una solución que funcione más rápido, intente esto. (Funciona como una categoría en UIFont)

+ (UIFont*)fontWithName:(NSString *)fontName minSize:(CGFloat)minSize maxSize:(CGFloat)maxSize constrainedToSize:(CGSize)labelSize forText:(NSString*)text { 

UIFont* font = [UIFont fontWithName:fontName size:maxSize]; 

CGSize constraintSize = CGSizeMake(labelSize.width, MAXFLOAT); 
NSRange range = NSMakeRange(minSize, maxSize); 

int fontSize = 0; 
for (NSInteger i = maxSize; i > minSize; i--) 
{ 
    fontSize = ceil(((float)range.length + (float)range.location)/2.0); 

    font = [font fontWithSize:fontSize]; 
    CGSize size = [text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap]; 

    if (size.height <= labelSize.height) 
     range.location = fontSize; 
    else 
     range.length = fontSize - 1; 

    if (range.length == range.location) 
    { 
     font = [font fontWithSize:range.location]; 
     break; 
    } 
} 

return font; 
} 
+1

Porque 'sizeWithFont:' se ha quedado obsoleto con iOS 7, deberías reemplazarlo por 'boundingRectWithSize:'. – AppsolutEinfach

4

En algunos casos, el cambio de "saltos de línea" de "Ajuste de línea" a "truncar la cola" puede ser todo lo que necesita, si se sabe el número de líneas que desea (ej. "2"): Crédito: Becky Hansmeyer

1

Existe una extensión ObjC en los comentarios, que calcula el tamaño de letra requerido para ajustar el texto de líneas múltiples en UILabel. Fue reescrito en Swift (puesto que es 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; 
    } 
} 

Enlace al código completo: https://gist.github.com/amayatsky/e6125a2288cc2e4f1bbf