¿Cómo comprobar si UILabel está truncado?

105

tengo un UILabel que puede tener diferentes longitudes dependiendo de si mi aplicación se está ejecutando en modo vertical u horizontal en un iPhone o iPad. Cuando el texto es demasiado largo para mostrarse en una línea y se trunca, quiero que el usuario pueda presionarlo y obtener una ventana emergente del texto completo.

¿Cómo puedo comprobar si se UILabelestá truncando el texto? ¿Es siquiera posible? En este momento solo estoy verificando diferentes longitudes según el modo en el que estoy, pero no funciona muy bien.

Randall
fuente
1
Eche un vistazo a la solución basada en el recuento de líneas que publiqué aquí
Claus
No puedo creer que después de todos estos años Apple aún no haya incorporado algo tan básico como esto en la UILabelAPI.
funct7

Respuestas:

108

Puede calcular el ancho de la cuerda y ver si el ancho es mayor quelabel.bounds.size.width

NSString UIKit Additions tiene varios métodos para calcular el tamaño de la cadena con una fuente específica. Sin embargo, si tiene un tamaño de fuente mínimo para su etiqueta que permite que el sistema reduzca el texto a ese tamaño. Es posible que desee utilizar sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: en ese caso.

CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}
progrmr
fuente
1
Gracias, esto es exactamente lo que necesitaba. La única diferencia fue sizeWithFont: devuelve un CGSize.
Randall
Ah, gracias por señalar eso, he corregido el código de muestra.
progrmr
16
sizeWithFont es un uso obsoleto: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}];
Amelia777
2
@fatuhoku numberOfLinesdevuelve el número máximo de líneas utilizadas para mostrar el texto como se describe en la UILabelreferencia de la clase: developer.apple.com/library/ios/documentation/UIKit/Reference/…
Paul
1
si la etiqueta tiene número de línea, intente multiplicar el ancho con el número de línea como este if (size.width> label.bounds.size.width * label.numberOfLines) {...}
Fadi Abuzant
89

Swift (como extensión) - funciona para uilabel multilínea:

swift4: (parámetro attributesde boundingRectcambiado ligeramente)

extension UILabel {

    var isTruncated: Bool {

        guard let labelText = text else {
            return false
        }

        let labelTextSize = (labelText as NSString).boundingRect(
            with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
            options: .usesLineFragmentOrigin,
            attributes: [.font: font],
            context: nil).size

        return labelTextSize.height > bounds.size.height
    }
}

swift3:

extension UILabel {

    var isTruncated: Bool {

        guard let labelText = text else { 
            return false
        }

        let labelTextSize = (labelText as NSString).boundingRect(
            with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
            options: .usesLineFragmentOrigin,
            attributes: [NSFontAttributeName: font],
            context: nil).size

        return labelTextSize.height > bounds.size.height
    }
}

swift2:

extension UILabel {

    func isTruncated() -> Bool {

        if let string = self.text {

            let size: CGSize = (string as NSString).boundingRectWithSize(
                CGSize(width: self.frame.size.width, height: CGFloat(FLT_MAX)),
                options: NSStringDrawingOptions.UsesLineFragmentOrigin,
                attributes: [NSFontAttributeName: self.font],
                context: nil).size

            if (size.height > self.bounds.size.height) {
                return true
            }
        }

        return false
    }

}
Robin
fuente
reemplace la altura de 999999.0 con CGFloat (FLT_MAX)
luz ambiental
2
para swift 3 usaría CGFloat.greatestFiniteMagnitude
zero3nna
2
buena respuesta, pero es mejor usar la propiedad calculada en lugar de func: var isTruncated: Bool {if let string = self.text {let size: CGSize = (string as NSString) .boundingRect (with: CGSize (width: self.frame.size .width, height: CGFloat.greatestFiniteMagnitude), opciones: NSStringDrawingOptions.usesLineFragmentOrigin, atributos: [NSFontAttributeName: self.font], context: nil) .size return (size.height> self.bounds.size.height)} return false}
Mohammadalijf
1
Esto no funcionó para mí porque estaba usando un NSAttributedString. Para conseguir que funcione, que necesitaba para modificar el valor de los atributos de uso: attributedText .attributes (a: 0, effectiveRange: cero)?
pulse4life
1
Gran respuesta, tuve que cambiarlo un poco para mis requisitos, pero funcionó perfectamente. También estaba usando una cadena de atributos, así que para los atributos que usé: (attributeText? .Attributes (at: 0, EffectRange: nil) ?? [.font: font]), solo asegúrese de verificar si el labelText no está vacío cuando usando esta solución.
Crt Gregoric
20

EDITAR: Acabo de ver que mi respuesta fue votada a favor, pero el fragmento de código que di está obsoleto.
Ahora, la mejor manera de hacer esto es (ARC):

NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = mylabel.lineBreakMode;
NSDictionary *attributes = @{NSFontAttributeName : mylabel.font,
                             NSParagraphStyleAttributeName : paragraph};
CGSize constrainedSize = CGSizeMake(mylabel.bounds.size.width, NSIntegerMax);
CGRect rect = [mylabel.text boundingRectWithSize:constrainedSize
                                         options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                      attributes:attributes context:nil];
if (rect.size.height > mylabel.bounds.size.height) {
    NSLog(@"TOO MUCH");
}

Tenga en cuenta que el tamaño calculado no es un valor entero. Entonces, si hace cosas como int height = rect.size.heightestas, perderá algo de precisión de punto flotante y es posible que tenga resultados incorrectos.

Respuesta anterior (obsoleta):

Si su etiqueta es multilínea, puede usar este código:

CGSize perfectSize = [mylabel.text sizeWithFont:mylabel.font constrainedToSize:CGSizeMake(mylabel.bounds.size.width, NSIntegerMax) lineBreakMode:mylabel.lineBreakMode];
if (perfectSize.height > mylabel.bounds.size.height) {
    NSLog(@"TOO MUCH");
}
Martín
fuente
13

puedes hacer una categoría con UILabel

- (BOOL)isTextTruncated

{
    CGRect testBounds = self.bounds;
    testBounds.size.height = NSIntegerMax;
    CGRect limitActual = [self textRectForBounds:[self bounds] limitedToNumberOfLines:self.numberOfLines];
    CGRect limitTest = [self textRectForBounds:testBounds limitedToNumberOfLines:self.numberOfLines + 1];
    return limitTest.size.height>limitActual.size.height;
}
DongXu
fuente
3
del documento: textRectForBounds:limitedToNumberOfLines:"No debe llamar a este método directamente" ...
Martin
@Martin gracias, veo que, el implemento aquí es
limitado
¿Por qué hace +1 al configurar limitedToNumberOfLines?
Ash
@Ash para comprobar si la etiqueta es más alta cuando se permite más espacio para el texto.
vrwim
1
Gracias por este código, funcionó para mí, aparte de algunos casos de borde al usar el diseño automático. Los arreglé agregando: setNeedsLayout() layoutIfNeeded()al comienzo del método.
Jovan Jovanovski
9

Utilice esta categoría para averiguar si una etiqueta está truncada en iOS 7 y superior.

// UILabel+Truncation.h
@interface UILabel (Truncation)

@property (nonatomic, readonly) BOOL isTruncated;

@end


// UILabel+Truncation.m
@implementation UILabel (Truncation)

- (BOOL)isTruncated
{
    CGSize sizeOfText =
      [self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)
                               options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
                            attributes:@{ NSFontAttributeName : label.font } 
                               context: nil].size;

    if (self.frame.size.height < ceilf(sizeOfText.height))
    {
        return YES;
    }
    return NO;
}

@end
Rajesh
fuente
9

Swift 3

Puede contar el número de líneas después de asignar la cadena y compararlo con el número máximo de líneas de la etiqueta.

import Foundation
import UIKit

extension UILabel {

    func countLabelLines() -> Int {
        // Call self.layoutIfNeeded() if your view is uses auto layout
        let myText = self.text! as NSString
        let attributes = [NSFontAttributeName : self.font]

        let labelSize = myText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil)
        return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
    }

    func isTruncated() -> Bool {

        if (self.countLabelLines() > self.numberOfLines) {
            return true
        }
        return false
    }
}
Claus
fuente
Esta es la buena respuesta para Swift. Gracias.
JimmyLee
8

Para agregar a la respuesta de iDev , debe usar en intrinsicContentSizelugar de frame, para que funcione para Autolayout

- (BOOL)isTruncated:(UILabel *)label{
        CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.intrinsicContentSize.width, CGFLOAT_MAX)
                                                     options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                  attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;

        if (self.intrinsicContentSize.height < ceilf(sizeOfText.height)) {
        return YES;
    }
    return NO;
}
onmyway133
fuente
¡Gracias! El uso de intrinsicContentSize en lugar de frame fue la solución a mi problema cuando la altura de UILabel es en realidad suficiente para ajustarse al texto, pero tiene un número limitado de líneas y, por lo tanto, todavía se está truncando.
Anton Matosov
Por alguna razón, esto devuelve NO incluso cuando el texto no encaja en la etiqueta
Can Poyrazoğlu
6

Eso es todo. Esto funciona con attributedText, antes de volver a la normalidadtext , lo que tiene mucho sentido para nosotros, las personas que tratamos con múltiples familias de fuentes, tamaños e incluso NSTextAttachments.

Funciona bien con el diseño automático, pero obviamente las restricciones deben definirse y establecerse antes de que verifiquemos isTruncated, de lo contrario, la etiqueta en sí ni siquiera sabrá cómo distribuirse, por lo que de ninguna manera sabría si está truncada.

No funciona abordar este problema con un simple NSStringy sizeThatFits. No estoy seguro de cómo la gente estaba obteniendo resultados positivos como ese. Por cierto, como se mencionó en numerosas ocasiones, el uso sizeThatFitsno es ideal en absoluto porque tiene en cuenta numberOfLinesel tamaño resultante, lo que anula todo el propósito de lo que estamos tratando de hacer, porque isTruncatedsiempre volvería falseindependientemente de si está truncado o no.

extension UILabel {
    var isTruncated: Bool {
        layoutIfNeeded()

        let rectBounds = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
        var fullTextHeight: CGFloat?

        if attributedText != nil {
            fullTextHeight = attributedText?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, context: nil).size.height
        } else {
            fullTextHeight = text?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil).size.height
        }

        return (fullTextHeight ?? 0) > bounds.size.height
    }
}
Lucas
fuente
esto funciona bien para mí gracias !! ¿Hay alguna actualización o modificación para esto?
amodkanthe
Gran respuesta. El único problema es que el texto atribuido nunca es nulo, incluso cuando no lo configura. Entonces, implementé esto como dos vars isTextTruncated y isAttributedTextTruncated.
Harris
@Harris dice que el valor predeterminado es nulo en el archivo uilabel.h
Lucas
@LucasChwe Lo sé, pero no es cierto en la práctica. Puede probarlo usted mismo y verlo. Para su información, forums.developer.apple.com/thread/118581
Harris
3

Aquí está la respuesta seleccionada en Swift 3 (como una extensión). El OP estaba preguntando por etiquetas de 1 línea. Muchas de las respuestas rápidas que probé aquí son específicas para etiquetas de varias líneas y no se marcan correctamente en las etiquetas de una sola línea.

extension UILabel {
    var isTruncated: Bool {
        guard let labelText = text as? NSString else {
            return false
        }
        let size = labelText.size(attributes: [NSFontAttributeName: font])
        return size.width > self.bounds.width
    }
}
Travis M.
fuente
La respuesta de Axel no funcionó para esto. Este lo hizo. ¡Gracias!
Glenn
2

Esto funciona para iOS 8:

CGSize size = [label.text boundingRectWithSize:CGSizeMake(label.bounds.size.width, NSIntegerMax) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : label.font} context:nil].size;

if (size.height > label.frame.size.height) {
    NSLog(@"truncated");
}
Marcio Fonseca
fuente
2

He escrito una categoría para trabajar con el truncamiento de UILabel. Funciona en iOS 7 y posterior. Espero eso ayude ! truncamiento de la cola de uilabel

@implementation UILabel (Truncation)

- (NSRange)truncatedRange
{
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];

    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];

    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
    textContainer.lineFragmentPadding = 0;
    [layoutManager addTextContainer:textContainer];

    NSRange truncatedrange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:0];
    return truncatedrange;
}

- (BOOL)isTruncated
{
    return [self truncatedRange].location != NSNotFound;
}

- (NSString *)truncatedText
{
    NSRange truncatedrange = [self truncatedRange];
    if (truncatedrange.location != NSNotFound)
    {
        return [self.text substringWithRange:truncatedrange];
    }

    return nil;
}

@end
Alejandro Cotilla
fuente
cada vez que da NSNotFound solamente
Dari
2
extension UILabel {

public func resizeIfNeeded() -> CGFloat? {
    guard let text = text, !text.isEmpty else { return nil }

    if isTruncated() {
        numberOfLines = 0
        sizeToFit()
        return frame.height
    }
    return nil
}

func isTruncated() -> Bool {
    guard let text = text, !text.isEmpty else { return false }

    let size: CGSize = text.size(withAttributes: [NSAttributedStringKey.font: font])
    return size.width > self.bounds.size.width
    }
}

Puede calcular el ancho de la cadena y ver si el ancho es mayor que el ancho de la etiqueta.

Hayk Brsoyan
fuente
1

Para agregar a lo que hizo @iDev , modifiqué el self.frame.size.heightuso label.frame.size.heighty tampoco lo uséNSStringDrawingUsesLineFontLeading . Después de esas modificaciones, logré un cálculo perfecto de cuándo ocurriría el truncamiento (al menos en mi caso).

- (BOOL)isTruncated:(UILabel *)label {
    CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.bounds.size.width, CGFLOAT_MAX)
                                                 options: (NSStringDrawingUsesLineFragmentOrigin)
                                              attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;

    if (label.frame.size.height < ceilf(sizeOfText.height)) {
        return YES;
    }
    return NO;
}
kgaidis
fuente
1

Tuve problemas boundingRect(with:options:attributes:context:)al usar el diseño automático (para establecer una altura máxima) y un texto atribuido conNSParagraph.lineSpacing

Se ignoró el espacio entre líneas (incluso cuando se pasó attributesal boundingRectmétodo) por lo que la etiqueta podría considerarse como no truncada cuando lo estaba.

La solución que encontré es usar UIView.sizeThatFits:

extension UILabel {
    var isTruncated: Bool {
        layoutIfNeeded()
        let heightThatFits = sizeThatFits(bounds.size).height
        return heightThatFits > bounds.size.height
    }
}
Axel Guilmin
fuente
0

Debido a que todas las respuestas anteriores usan métodos depreciados, pensé que esto podría ser útil:

- (BOOL)isLabelTruncated:(UILabel *)label
{
    BOOL isTruncated = NO;

    CGRect labelSize = [label.text boundingRectWithSize:CGSizeFromString(label.text) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : label.font} context:nil];

    if (labelSize.size.width / labelSize.size.height > label.numberOfLines) {

        isTruncated = YES;
    }

    return isTruncated;
}
Raz
fuente
Está dividiendo el ancho por el alto y si es mayor que el número de línea (que podría ser 0), está diciendo que la etiqueta está truncada. No hay forma de que esto funcione.
Can Leloğlu
@ CanLeloğlu, por favor pruébelo. Es un ejemplo práctico.
Raz
2
¿Qué pasa si numberOfLines es igual a cero?
Can Leloğlu
0

Para manejar iOS 6 (sí, algunos de nosotros todavía tenemos que hacerlo), aquí hay otra expansión a la respuesta de @ iDev. La conclusión clave es que, para iOS 6, para asegurarse de que el numberOfLines de su UILabel esté configurado en 0 antes de llamar a sizeThatFits; si no, le dará un resultado que dice que se necesitan "los puntos para dibujar numberOfLines de altura" para dibujar el texto de la etiqueta.

- (BOOL)isTruncated
{
    CGSize sizeOfText;

    // iOS 7 & 8
    if([self.text respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)])
    {
        sizeOfText = [self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)
                                             options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                          attributes:@{NSFontAttributeName:self.font}
                                             context:nil].size;
    }
    // iOS 6
    else
    {
        // For iOS6, set numberOfLines to 0 (i.e. draw label text using as many lines as it takes)
        //  so that siteThatFits works correctly. If we leave it = 1 (for example), it'll come
        //  back telling us that we only need 1 line!
        NSInteger origNumLines = self.numberOfLines;
        self.numberOfLines = 0;
        sizeOfText = [self sizeThatFits:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)];
        self.numberOfLines = origNumLines;
    }

    return ((self.bounds.size.height < sizeOfText.height) ? YES : NO);
}
John Jacecko
fuente
0

Asegúrese de llamar a cualquiera de estos en viewDidLayoutSubviews.

public extension UILabel {

    var isTextTruncated: Bool {
        layoutIfNeeded()
        return text?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil).size.height ?? 0 > bounds.size.height
    }

    var isAttributedTextTruncated: Bool {
        layoutIfNeeded()
        return attributedText?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size.height ?? 0 > bounds.size.height
    }

}
Harris
fuente
0

SWIFT 5

Ejemplo de un UILabel de múltiples líneas que está configurado para mostrar solo 3 líneas.

    let labelSize: CGSize = myLabel.text!.size(withAttributes: [.font: UIFont.systemFont(ofSize: 14, weight: .regular)])

    if labelSize.width > myLabel.intrinsicContentSize.width * 3 {
        // your label will truncate
    }

Aunque el usuario puede seleccionar la tecla de retorno agregando una línea adicional sin agregar al "ancho del texto", en ese caso algo como esto también puede ser útil.

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

    if text == "\n" {
        // return pressed 

    }
}
Waylan Sands
fuente
-1

Solución Swift 3

Creo que la mejor solución es (1) crear un archivo UILabelcon las mismas propiedades que la etiqueta que está verificando para el truncamiento, (2) llamar .sizeToFit(), (3) comparar los atributos de la etiqueta ficticia con su etiqueta real.

Por ejemplo, si desea verificar si una etiqueta de una línea que tiene un ancho variable se trunca o no, puede usar esta extensión:

extension UILabel {
    func isTruncated() -> Bool {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: CGFloat.greatestFiniteMagnitude, height: self.bounds.height))
        label.numberOfLines = 1
        label.font = self.font
        label.text = self.text
        label.sizeToFit()
        if label.frame.width > self.frame.width {
            return true
        } else {
            return false
        }
    }
}

... pero de nuevo, puede modificar fácilmente el código anterior para adaptarlo a sus necesidades. Digamos que su etiqueta tiene varias líneas y altura variable. Entonces la extensión se vería así:

extension UILabel {
    func isTruncated() -> Bool {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        label.numberOfLines = 0
        label.font = self.font
        label.text = self.text
        label.sizeToFit()
        if label.frame.height > self.frame.height {
            return true
        } else {
            return false
        }
    }
}
Saoud Rizwan
fuente
-6

¿No sería fácil establecer el atributo de título para la etiqueta? Si lo configura, se mostrará la etiqueta completa cuando se mantenga el cursor sobre ella.

puede calcular la longitud de la etiqueta y el ancho de div (convertir a longitud - jQuery / Javascript - ¿Cómo convierto un valor de píxel (20px) a un valor numérico (20) ).

configure jquery para establecer el título si la longitud es mayor que el ancho div.

var divlen = parseInt(jQuery("#yourdivid").width,10);
var lablen =jQuery("#yourlabelid").text().length;
if(lablen < divlen){
jQuery("#yourlabelid").attr("title",jQuery("#yourlabelid").text());
}
usuario3405326
fuente