Como verificar se UILabel está truncado?

105

Eu tenho um UILabelque pode ter comprimentos variados dependendo se meu aplicativo está ou não sendo executado no modo retrato ou paisagem em um iPhone ou iPad. Quando o texto é muito longo para ser exibido em uma linha e trunca, quero que o usuário possa pressioná-lo e obter um pop-up do texto completo.

Como posso verificar se o UILabelestá truncando o texto? É mesmo possível? No momento, estou apenas verificando comprimentos diferentes com base no modo em que estou, mas não funciona muito bem.

Randall
fonte
1
Dê uma olhada na solução com base na contagem de linhas que postei aqui
Natal
Não posso acreditar que depois de todos esses anos a Apple ainda não incorporou algo tão básico quanto isso à UILabelAPI.
funct7

Respostas:

108

Você pode calcular a largura da corda e ver se a largura é maior quelabel.bounds.size.width

O NSString UIKit Additions possui vários métodos para calcular o tamanho da string com uma fonte específica. No entanto, se você tiver um minimumFontSize para sua etiqueta que permita que o sistema reduza o texto a esse tamanho. Você pode querer usar sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: nesse caso.

CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}
progrmr
fonte
1
Obrigado, isso é exatamente o que eu precisava. A única diferença era, sizeWithFont: retorna um CGSize.
Randall
Ah, obrigado por apontar isso, eu corrigi o código de amostra.
progrmr
16
sizeWithFont está obsoleto, use: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}];
Amelia777
2
@fatuhoku numberOfLinesretorna o número máximo de linhas usadas para exibir o texto, conforme descrito na UILabelreferência da classe: developer.apple.com/library/ios/documentation/UIKit/Reference/…
Paul
1
se o rótulo tem número de linha, experimente multiplicar a largura pelo número de linha como este if (size.width> label.bounds.size.width * label.numberOfLines) {...}
Fadi Abuzant
89

Swift (como extensão) - funciona para uilabel multilinhas:

swift4: ( attributesparâmetro boundingRectligeiramente alterado)

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
fonte
substituir altura de 999.999,0 com CGFloat (FLT_MAX)
AmbientLight
2
para o swift 3, eu usaria CGFloat.greatestFiniteMagnitude
zero3nna 01 de
2
boa resposta, mas é melhor usar a propriedade calculada em vez de func: var isTruncated: Bool {if let string = self.text {let size: CGSize = (string as NSString) .boundingRect (com: CGSize (width: self.frame.size .width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attribute: [NSFontAttributeName: self.font], context: nil) .size return (size.height> self.bounds.size.height)} return false}
Mohammadalijf
1
Isso não funcionou para mim porque eu estava usando um NSAttributedString. Para fazê-lo funcionar, eu precisei modificar o valor dos atributos a usar: attributeText? .Attributes (at: 0, effectiveRange: nil)
pulse4life
1
Ótima resposta, tive que mudar um pouco para minhas necessidades, mas funcionou perfeitamente. Eu também estava usando uma string atribuída - então, para os atributos que usei: (attributeText? .Attributes (at: 0, effectiveRange: nil) ?? [.font: font]), apenas certifique-se de verificar se o labelText não está vazio quando usando esta solução.
Crt Gregoric
20

EDIT: Acabei de ver que minha resposta foi votada positivamente, mas o trecho de código que forneci está obsoleto.
Agora, a melhor maneira de fazer isso é (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");
}

Observe que o tamanho calculado não é um valor inteiro. Portanto, se você fizer coisas como int height = rect.size.height, perderá alguma precisão de ponto flutuante e poderá ter resultados errados.

Resposta antiga (obsoleta):

Se o seu rótulo for multilinha, você pode 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");
}
Martin
fonte
13

você pode fazer uma categoria com 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
fonte
3
do documento: textRectForBounds:limitedToNumberOfLines:"Você não deve chamar este método diretamente" ...
Martin
@Martin obrigado, vejo que, o implemento aqui é limitado. Mas quando você chama este método depois de definir limites e texto, ele funcionará bem
DongXu
Por que você marca o +1 ao definir o limitedToNumberOfLines?
Ash
@Ash para verificar se o rótulo é mais alto quando é permitido mais espaço para o texto.
vrwim
1
Obrigado por este código, ele funcionou para mim além de alguns casos marginais ao usar o layout automático. Eu os consertei adicionando: setNeedsLayout() layoutIfNeeded()no início do método.
Jovan Jovanovski
9

Use esta categoria para descobrir se um rótulo está truncado no iOS 7 e 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
fonte
9

Swift 3

Você pode contar o número de linhas após atribuir a string e comparar com o número máximo de linhas do rótulo.

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
    }
}
Noel
fonte
Esta é uma boa resposta para o rápido. Obrigado.
JimmyLee
8

Para adicionar à resposta do iDev , você deve usar em intrinsicContentSizevez de frame, para fazer funcionar 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
fonte
Obrigado! O uso de intrinsicContentSize em vez de frame foi a solução para meu problema quando a altura do UILabel é realmente suficiente para caber no texto, mas tem um número limitado de linhas e, portanto, ainda está truncado.
Anton Matosov
Por algum motivo, isso está retornando NÃO, mesmo quando o texto não se encaixa no rótulo
Can Poyrazoğlu
6

É isso. Isso funciona attributedText, antes de voltar ao normal text, o que faz muito sentido para nós, pessoas que lidamos com várias famílias de fontes, tamanhos e até mesmo NSTextAttachments!

Funciona bem com autolayout, mas obviamente as restrições devem ser definidas e configuradas antes de verificarmos isTruncated, caso contrário, o rótulo em si nem mesmo saberá como se dispor, então de forma alguma saberia se está truncado.

Não funciona abordar esse problema com apenas um NSStringe sizeThatFits. Não tenho certeza de como as pessoas estão obtendo resultados positivos como esse. BTW, como mencionei inúmeras vezes, usar sizeThatFitsnão é o ideal de forma alguma, pois leva em consideração numberOfLineso tamanho resultante, o que anula todo o propósito do que estamos tentando fazer, pois isTruncatedsempre retornaria falseindependentemente de estar truncado ou não.

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
fonte
está funcionando bem para mim, obrigado! existem atualizações ou modificações para isso?
amodkanthe
Ótima resposta. O único problema é attributeText nunca é nulo, mesmo quando você não o define. Portanto, implementei isso como dois vars isTextTruncated e isAttributedTextTruncated.
Harris
@Harris diz que o padrão é nulo no arquivo uilabel.h
Lucas
@LucasChwe eu sei, mas não é verdade na prática. Você pode experimentar por si mesmo e ver. fyi, forums.developer.apple.com/thread/118581
Harris,
3

Aqui está a resposta selecionada no Swift 3 (como uma extensão). O OP estava perguntando sobre rótulos de 1 linha. Muitas das respostas rápidas que tentei aqui são específicas para rótulos de várias linhas e não estão sinalizando corretamente em rótulos de uma única linha.

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.
fonte
A resposta de Axel não funcionou para isso. Este sim. Obrigado!
Glenn
2

Isso 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
fonte
2

Eu escrevi uma categoria para trabalhar com o truncamento UILabel. Funciona no iOS 7 e posterior. Espero que ajude ! truncamento de cauda 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
fonte
sempre que dá apenas NSNotFound
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
    }
}

Você pode calcular a largura da string e ver se a largura é maior do que a largura do rótulo.

Hayk Brsoyan
fonte
1

Para adicionar ao que @iDev fez, eu modifiquei o self.frame.size.heightpara usar label.frame.size.heighte também não usei NSStringDrawingUsesLineFontLeading. Após essas modificações, consegui um cálculo perfeito de quando o truncamento aconteceria (pelo menos para o meu 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
fonte
1

Tive problemas boundingRect(with:options:attributes:context:)ao usar autolayout (para definir uma altura máxima) e um texto atribuído comNSParagraph.lineSpacing

O espaçamento entre as linhas foi ignorado (mesmo quando passado attributespara o boundingRectmétodo), portanto, o rótulo pode ser considerado como não truncado quando estava.

A solução que encontrei é usar UIView.sizeThatFits:

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

Como todas as respostas acima usam métodos depreciados, achei que isso poderia 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
fonte
Você está dividindo a largura pela altura e, se for maior que o número de linha (que pode muito bem ser 0), você está dizendo que o rótulo está truncado. Não há como isso funcionar.
Can Leloğlu
@ CanLeloğlu, por favor, teste. É um exemplo prático.
Raz
2
E se numberOfLines for igual a zero?
Can Leloğlu
0

Para lidar com o iOS 6 (sim, alguns de nós ainda precisam), aqui está mais uma expansão para a resposta do @iDev. A principal conclusão é que, para iOS 6, certifique-se de que o numberOfLines de seu UILabel esteja definido como 0 antes de chamar sizeThatFits; se não, ele lhe dará um resultado que diz que "os pontos para desenhar numberOfLines com altura" são necessários para desenhar o texto do rótulo.

- (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
fonte
0

Certifique-se de chamar qualquer um deles em 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
fonte
0

SWIFT 5

Exemplo para um UILabel de múltiplas linhas que é configurado para exibir apenas 3 linhas.

    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
    }

Embora o usuário possa selecionar a tecla de retorno, adicionando uma linha extra sem adicionar à "largura do texto", nesse caso algo como isso também pode ser útil.

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

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

    }
}
Waylan Sands
fonte
-1

Solução Swift 3

Acho que a melhor solução é (1) criar um UILabelcom as mesmas propriedades do rótulo que você está verificando para truncamento, (2) chamar .sizeToFit(), (3) comparar os atributos do rótulo fictício com seu rótulo real.

Por exemplo, se você quiser verificar se uma etiqueta com uma linha com largura variável trunca ou não, você pode usar esta extensão:

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

... mas, novamente, você pode modificar facilmente o código acima para atender às suas necessidades. Então, digamos que sua etiqueta seja multilinha e tenha altura variável. Então, a extensão seria algo assim:

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
fonte
-6

Não seria fácil definir o atributo de título para o rótulo? Definir isso exibirá o rótulo completo ao passar o mouse.

você pode calcular o comprimento do rótulo e a largura div (converter para comprimento - jQuery / Javascript - como faço para converter um valor de pixel (20px) para um valor numérico (20) ).

defina jquery para definir o título se o comprimento for maior que a largura div.

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