2010-11-02 9 views
18

Tengo un UILabel cuyo tamaño se calcula con el método sizeWithFont:. El modo de salto de línea se establece en UILineBreakModeWordWrap (se usa el mismo indicador al calcular el tamaño con sizeWithFont:) ...Líneas resultantes de UILabel con UILineBreakModeWordWrap

Todo funciona muy bien, la etiqueta tiene el tamaño adecuado y muestra mi texto según sea necesario.

Ahora necesito conocer las líneas que se usan para mostrar la etiqueta (o las líneas que se generan cuando se usa sizeWithFont:). Técnicamente podría escribir mi propia implementación de salto de línea basada en espacios/retornos de intercalación, pero luego no se garantizará de la misma manera que la implementación de Apple y, por lo tanto, las líneas resultantes no serán las que se usarán para calcular el tamaño del texto. , no importa el hecho de reinventar la rueda.

Lo ideal es que pase la cadena, especifique el ancho y el modo de salto de línea y reciba una serie de cadenas que representen las líneas de texto visuales.

¿Alguna idea de cómo hacer que esto suceda de la manera más elegante?

Respuesta

24

No creo que haya ninguna bala de plata para esto.

Aquí hay un método de categoría que parece funcionar para los pocos casos de prueba básicos que arrojé. ¡No hay garantías de que no rompa con algo complejo!

La forma en que funciona es pasar a través de la prueba de cadena para ver si un rango de palabras se ajusta al ancho de la etiqueta. Cuando calcula que el rango actual es demasiado ancho, registra el rango del último ajuste como una línea.

No pretendo que esto sea eficiente. Una mejor manera sólo puede ser la de poner en práctica su propia UILabel ...

@interface UILabel (Extensions) 

- (NSArray*) lines; 

@end 

@implementation UILabel (Extensions) 

- (NSArray*) lines 
{ 
    if (self.lineBreakMode != UILineBreakModeWordWrap) 
    { 
     return nil; 
    } 

    NSMutableArray* lines = [NSMutableArray arrayWithCapacity:10]; 

    NSCharacterSet* wordSeparators = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 

    NSString* currentLine = self.text; 
    int textLength = [self.text length]; 

    NSRange rCurrentLine = NSMakeRange(0, textLength); 
    NSRange rWhitespace = NSMakeRange(0,0); 
    NSRange rRemainingText = NSMakeRange(0, textLength); 
    BOOL done = NO; 
    while (!done) 
    { 
     // determine the next whitespace word separator position 
     rWhitespace.location = rWhitespace.location + rWhitespace.length; 
     rWhitespace.length = textLength - rWhitespace.location; 
     rWhitespace = [self.text rangeOfCharacterFromSet: wordSeparators options: NSCaseInsensitiveSearch range: rWhitespace]; 
     if (rWhitespace.location == NSNotFound) 
     { 
      rWhitespace.location = textLength; 
      done = YES; 
     } 

     NSRange rTest = NSMakeRange(rRemainingText.location, rWhitespace.location-rRemainingText.location); 

     NSString* textTest = [self.text substringWithRange: rTest]; 

     CGSize sizeTest = [textTest sizeWithFont: self.font forWidth: 1024.0 lineBreakMode: UILineBreakModeWordWrap]; 
     if (sizeTest.width > self.bounds.size.width) 
     { 
      [lines addObject: [currentLine stringByTrimmingCharactersInSet:wordSeparators]]; 
      rRemainingText.location = rCurrentLine.location + rCurrentLine.length; 
      rRemainingText.length = textLength-rRemainingText.location; 
      continue; 
     } 

     rCurrentLine = rTest; 
     currentLine = textTest; 
    } 

    [lines addObject: [currentLine stringByTrimmingCharactersInSet:wordSeparators]]; 

    return lines; 
} 

@end 

uso como esto:

NSArray* lines = [_theLabel lines]; 

int count = [lines count]; 
+1

Gracias TomSwift para su entrada ... Este tipo de obras para algunos casos, pero definitivamente me dio un gran impulso, estoy trabajando en una solución a prueba de balas más basado en el código de . ¡Gracias de nuevo! – Nick

+0

¿Qué casos lo rompieron? – TomSwift

+0

Buena solución TomSwift. Pero no funciona para mí, siempre es el texto en una línea, aunque la cuenta int a veces es 9, 10 o 20. Mi código: \t self.labelDescription.text = event.description; \t self.labelDescription.lineBreakMode = UILineBreakModeWordWrap; \t self.labelDescription.font = [UIFont fontWithName: @ "Helvetica" size: 13.0]; \t \t NSArray * lines = [self.labelDescription lines]; \t \t int count = [recuento de líneas]; \t self.labelDescription.numberOfLines = count; –

38

para calcular el número de líneas que tiene un UILabel después de concluir que es el texto que va a necesitar para encontrar la delantera (altura de línea) de la fuente UILabel (label.font.leading) y luego divida la altura de su línea múltiple UILabel por la altura de cada línea para obtener el número de líneas.

He aquí un ejemplo:

- (void)viewDidLoad { 

    [super viewDidLoad]; 

    UILabel *label = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease]; 
    label.numberOfLines = 0; 
    label.lineBreakMode = UILineBreakModeWordWrap; 
    label.text = @"Some really really long string that will cause the label's text to wrap and wrap and wrap around. Some really really long string that will cause the label's text to wrap and wrap and wrap around."; 

    CGRect frame = label.frame; 
    frame.size.width = 150.0f; 
    frame.size = [label sizeThatFits:frame.size]; 
    label.frame = frame; 

    CGFloat lineHeight = label.font.leading; 
    NSUInteger linesInLabel = floor(frame.size.height/lineHeight); 
    NSLog(@"Number of lines in label: %i", linesInLabel); 

    [self.view addSubview:label]; 

} 

O bien, puede hacerlo en dos líneas:

[label sizeToFit]; 
int numLines = (int)(label.frame.size.height/label.font.leading); 
+0

ah, esto es muy inteligente. más simple que mi solución, ¡seguro! – TomSwift

+3

Tenga en cuenta que ** "leading" ** se pronuncia "ledding". (Exactamente como en "Led Zepplin", NO como en "seguir al líder".) Simplemente significa el metal, led (es decir, la sustancia atómica Pb): la palabra proviene de los primeros días de la tipografía cuando literalmente se ponían tiras de led entre las letras para crear el, bueno, ledding. – Fattie

+0

Gracias .... [label sizeToFit]; int numLines = (int) (label.frame.size.height/label.font.leading); funcionó para mí –

11

Sólo tiene que llamar por debajo método y pase ya sea UILabel o UITextView:

-(NSInteger)getNumberOfLinesInLabelOrTextView:(id)obj 
{ 
    NSInteger lineCount = 0; 
    if([obj isKindOfClass:[UILabel class]]) 
    { 
     UILabel *label = (UILabel *)obj; 

     // This method is deprecated in iOS 7.0 or later 
     // CGSize requiredSize = [label.text sizeWithFont:label.font constrainedToSize:label.frame.size lineBreakMode:label.lineBreakMode]; 

     CGSize requiredSize = [label.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(label.frame), CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:label.font} context:nil].size; 

     int charSize = label.font.leading; 
     int rHeight = requiredSize.height; 

     lineCount = rHeight/charSize; 
    } 
    else if ([obj isKindOfClass:[UITextView class]]) 
    { 
     UITextView *textView = (UITextView *)obj; 
     lineCount = textView.contentSize.height/textView.font.leading; 
    } 

    return lineCount; 
} 

Ahora llame a este método: -

NSLog(@"%d",[self getNumberOfLinesInLabelOrTextView:label]); 
NSLog(@"%d",[self getNumberOfLinesInLabelOrTextView:textView]); 

ACTUALIZADO: CODIGO SWIFT

func getNumberOfLinesInLabelOrTextView(obj:AnyObject) -> NSInteger { 

    var lineCount: NSInteger = 0 
    if (obj.isKindOfClass(UILabel)) { 

     let label: UILabel = obj as! UILabel 
     let requiredSize: CGSize = (label.text)!.boundingRectWithSize(CGSizeMake(CGRectGetWidth(label.frame), CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: label.font], context: nil).size 
     let charSize: CGFloat = label.font.leading 
     let rHeight: CGFloat = requiredSize.height 
     lineCount = (NSInteger)(rHeight/charSize) 
    } 
    else if (obj.isKindOfClass(UITextView)){ 

     let textView: UITextView = obj as! UITextView 
     lineCount = (NSInteger)(textView.contentSize.height/textView.font.leading) 
    } 

    return lineCount 
} 

Ahora llama a este método: -

println("%d \(self.getNumberOfLinesInLabelOrTextView(textView))") 
println("%d \(self.getNumberOfLinesInLabelOrTextView(label))") 

Nota: leading - utilizar lineHeight. no devuelve líder real. será formalmente obsoleto en el futuro.

+0

Esto funcionó bien, pero cuando actualizo xcode a 7 se muestra el error "exc_arithmetic code = exc_i386_div" en la línea lineCount = rHeight/charSize; . Así que hice lineCount = rHeight/1; ahora funciona. Si me equivoco, guíame uno correcto :) – user3589771

0

de Xcode 7 y hasta la respuesta de thetiger necesita una actualización comentado sobre el código de abajo:

-(NSInteger)getNumberOfLinesInLabelOrTextView:(id)obj 
{ 
    NSInteger lineCount = 0; 
    if([obj isKindOfClass:[UILabel class]]) 
    { 
     UILabel *label = (UILabel *)obj; 
     CGSize requiredSize = [label.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(label.frame), CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:label.font} context:nil].size; 

     int charSize = label.font.leading; 
     // now listen , you need to set the text or label with only 1 
     // then nslog(@"%d",charSize); 
     // then change the line int charSize = label.font.leading; into 
     // int charSize = the printed value in case of 1 line 
     int rHeight = requiredSize.height; 

     lineCount = rHeight/charSize; 
    } 
    else if ([obj isKindOfClass:[UITextView class]]) 
    { 
     UITextView *textView = (UITextView *)obj; 
     lineCount = textView.contentSize.height/textView.font.leading; 
    } 

    return lineCount; 
} 

esto sólo funcionará si está utilizando mismo tipo de letra y tamaño, no es una decisión inteligente, pero me ayudó y quería compartirlo como la solución actual sé

0

Actualizado para la Swift 3

para calcular el número de líneas que tiene UILabel, después de concluir su texto, es necesario di vea la altura de su UILabel multilínea por la altura de cada línea (ascender).

Al tratar la respuesta de Adolfo, por alguna razón, label.font.leading volvieron 0.0, por lo que se utiliza label.font.ascender, que devuelve la altura desde el inicio hasta la parte superior del marco de la UILabel 's. Ver la imagen a continuación.

enter image description here

//Makes label go to another line if necessary 
label.numberOfLines = 0 //Set num of lines to infinity 
label.lineBreakMode = .byWordWrapping 
label.sizeToFit() 
let numLines = Int(label.frame.size.height/label.font.ascender)