Come verificare se UILabel è troncato?


106

Ho una UILabellunghezza variabile a seconda che la mia app sia in esecuzione in modalità verticale o orizzontale su un iPhone o iPad. Quando il testo è troppo lungo per essere visualizzato su una riga e viene troncato, desidero che l'utente sia in grado di premerlo e ottenere un popup del testo completo.

Come posso verificare se UILabelil testo sta troncando? È anche possibile? In questo momento sto solo verificando lunghezze diverse in base alla modalità in cui mi trovo, ma non funziona molto bene.


1
Dai un'occhiata alla soluzione basata sul conteggio delle righe che ho pubblicato qui
Claus

Non riesco a credere che dopo tutti questi anni Apple non abbia ancora incorporato qualcosa di così semplice come questo UILabelnell'API.
funct7

Risposte:


108

Puoi calcolare la larghezza della stringa e vedere se la larghezza è maggiore dilabel.bounds.size.width

NSString UIKit Additions ha diversi metodi per calcolare la dimensione della stringa con un carattere specifico. Tuttavia, se hai un minimumFontSize per l'etichetta che consente al sistema di ridurre il testo fino a quella dimensione. Potresti voler usare sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: in questo caso.

CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}

1
Grazie, questo è esattamente ciò di cui avevo bisogno. L'unica differenza era sizeWithFont: restituisce un CGSize.
Randall

Ah, grazie per averlo fatto notare, ho corretto il codice di esempio.
progrmr

16
sizeWithFont è deprecato use: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}];
Amelia777

2
@fatuhoku numberOfLinesrestituisce il numero massimo di righe utilizzate per visualizzare il testo come indicato nel UILabelriferimento alla classe: developer.apple.com/library/ios/documentation/UIKit/Reference/…
Paul

1
se l'etichetta ha un numero di righe, prova a moltiplicare la larghezza con il numero di righe in questo modo if (size.width> label.bounds.size.width * label.numberOfLines) {...}
Fadi Abuzant

90

Swift (come estensione) - funziona per uilabel multi linea:

swift4: ( attributesparametro di boundingRectcambiato leggermente)

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
    }

}

sostituire l'altezza di 999999.0 con CGFloat (FLT_MAX)
ambientale

2
per swift 3 userei CGFloat.greatestFiniteMagnitude
zero3nna

2
bella risposta ma è meglio usare la proprietà calcolata invece di 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), opzioni: NSStringDrawingOptions.usesLineFragmentOrigin, attributi: [NSFontAttributeName: self.font], context: nil) .size return (size.height> self.bounds.size.height)} return false}
Mohammadalijf

1
Questo non ha funzionato per me perché stavo usando un NSAttributedString. Per farlo funzionare, avevo bisogno di modificare il valore degli attributi da utilizzare: attributeText? .Attributes (at: 0,
actualRange

1
Ottima risposta, ho dovuto cambiarlo un po 'per le mie esigenze, ma ha funzionato perfettamente. Stavo anche usando una stringa attribuita, quindi per gli attributi che ho usato: (attributeText? .Attributes (at: 0, actualRange: nil) ?? [.font: font]), assicurati solo di controllare se labelText non è vuoto quando utilizzando questa soluzione.
Crt Gregoric

20

EDIT: Ho appena visto che la mia risposta è stata votata positivamente, ma lo snippet di codice che ho fornito è deprecato.
Ora il modo migliore per farlo è (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");
}

Nota la dimensione calcolata non è un valore intero. Quindi, se fai cose come int height = rect.size.height, perderai un po 'di precisione in virgola mobile e potresti avere risultati errati.

Vecchia risposta (obsoleta):

Se la tua etichetta è multilinea, puoi utilizzare questo codice:

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

13

puoi creare una categoria 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;
}

3
dal doc: textRectForBounds:limitedToNumberOfLines:"Non dovresti chiamare direttamente questo metodo" ...
Martin

@ Martin grazie, lo vedo, l'attrezzo qui è limitato, ma quando chiami questo metodo dopo aver impostato limiti e testo, funzionerà bene
DongXu

Perché fai +1 quando imposti limitedToNumberOfLines?
Ash

@Ash per controllare se l'etichetta è più alta quando è consentito più spazio per il testo.
vrwim

1
Grazie per questo codice, ha funzionato per me a parte alcuni casi di confine quando si utilizza il layout automatico. Li ho risolti aggiungendo: setNeedsLayout() layoutIfNeeded()all'inizio del metodo.
Jovan Jovanovski

9

Usa questa categoria per scoprire se un'etichetta è troncata su iOS 7 e versioni successive.

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

9

Swift 3

È possibile contare il numero di righe dopo aver assegnato la stringa e confrontarlo con il numero massimo di righe dell'etichetta.

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

Questa è la bella risposta per Swift. Grazie.
JimmyLee

8

Per aggiungere alla risposta di iDev , dovresti usare al intrinsicContentSizeposto di frame, per farlo funzionare per l'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;
}

Grazie! L'utilizzo di intrinsicContentSize invece di frame è stata la soluzione al mio problema quando l'altezza di UILabel è effettivamente sufficiente per adattare il testo, ma ha un numero limitato di righe e quindi è ancora troncato.
Anton Matosov

Per qualche ragione, questo restituisce NO anche quando il testo non rientra nell'etichetta
Can Poyrazoğlu

6

Questo è. Funziona con attributedText, prima di tornare alla normalità text, il che ha molto senso per noi persone che gestiamo più famiglie di caratteri, dimensioni e persino NSTextAttachments!

Funziona bene con il layout automatico, ma ovviamente i vincoli devono essere definiti e impostati prima di controllare isTruncated, altrimenti l'etichetta stessa non saprebbe nemmeno come disporsi, quindi in nessun modo saprebbe nemmeno se è troncata.

Non funziona affrontare questo problema con un semplice NSStringe sizeThatFits. Non sono sicuro di come le persone stessero ottenendo risultati positivi come quello. A proposito, come accennato numerose volte, l'utilizzo sizeThatFitsnon è affatto l'ideale perché tiene conto numberOfLinesdella dimensione risultante, che vanifica l'intero scopo di ciò che stiamo cercando di fare, perché isTruncatedtornerebbe sempre a falseprescindere che sia troncato o meno.

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

questo funziona bene per me grazie !! ci sono aggiornamenti o modifiche per questo?
amodkanthe

Bella risposta. L'unico problema è attribuito Il testo non è mai nullo, anche quando non lo si imposta. Quindi, l'ho implementato come due variabili isTextTruncated e isAttributedTextTruncated.
Harris

@Harris dice che il valore predefinito è nullo nel file uilabel.h
Lucas

@ LucasChwe lo so, ma in pratica non è vero. Puoi provarlo di persona e vedere. Cordiali saluti
Harris

4

Ecco la risposta selezionata in Swift 3 (come estensione). L'OP chiedeva circa 1 etichette di riga. Molte delle risposte rapide che ho provato qui sono specifiche per le etichette su più righe e non vengono contrassegnate correttamente sulle etichette a riga singola.

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

La risposta di Axel non ha funzionato per questo. Questo lo ha fatto. Grazie!
Glenn

2

Funziona per 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");
}

2

Ho scritto una categoria per lavorare con il troncamento di UILabel. Funziona su iOS 7 e versioni successive. Spero che sia d'aiuto ! troncamento della coda 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

ogni volta che dà solo 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
    }
}

Puoi calcolare la larghezza della stringa e vedere se la larghezza è maggiore della larghezza dell'etichetta.


1

Per aggiungere a quello che ha fatto @iDev , ho modificato il self.frame.size.heightto use label.frame.size.heighte inoltre non l'ho usato NSStringDrawingUsesLineFontLeading. Dopo queste modifiche, ho ottenuto il calcolo perfetto di quando sarebbe avvenuto il troncamento (almeno per il mio 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;
}

1

Ho avuto problemi con l' boundingRect(with:options:attributes:context:)utilizzo del layout automatico (per impostare un'altezza massima) e un testo attribuito conNSParagraph.lineSpacing

La spaziatura tra le righe è stata ignorata (anche se passata attributesal boundingRectmetodo), quindi l'etichetta potrebbe essere considerata non troncata quando lo era.

La soluzione che ho trovato è usare UIView.sizeThatFits:

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

0

Poiché tutte le risposte precedenti utilizzano metodi ammortizzati, ho pensato che potesse essere utile:

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

Stai dividendo la larghezza per l'altezza e se è più grande del numero di linee (che potrebbe benissimo essere 0) stai dicendo che l'etichetta è troncata. Non è possibile che funzioni.
Can Leloğlu

@ CanLeloğlu per favore provalo. È un esempio funzionante.
Raz

2
E se numberOfLines fosse uguale a zero?
Can Leloğlu

0

Per gestire iOS 6 (sì, alcuni di noi devono ancora farlo), ecco un'altra espansione alla risposta di @ iDev. Il punto chiave è che, per iOS 6, assicurati che il numero di UILabel del tuo UILabel sia impostato su 0 prima di chiamare sizeThatFits; in caso contrario, ti darà un risultato che dice "i punti per disegnare numberOfLines che valgono l'altezza" sono necessari per disegnare il testo dell'etichetta.

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

0

Assicurati di chiamare uno di questi in 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
    }

}

0

SWIFT 5

Esempio di un'etichetta UIL a più righe impostata per visualizzare solo 3 righe.

    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
    }

Sebbene l'utente possa selezionare il tasto Invio aggiungendo una riga in più senza aggiungere alla "larghezza del testo", in tal caso potrebbe essere utile anche qualcosa di simile.

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

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

    }
}

-1

Soluzione Swift 3

Penso che la soluzione migliore sia (1) creare un UILabelcon le stesse proprietà dell'etichetta che stai controllando per il troncamento, (2) chiamare .sizeToFit(), (3) confrontare gli attributi dell'etichetta fittizia con l'etichetta effettiva.

Ad esempio, se desideri verificare se un'etichetta a una riga con larghezza variabile viene troncata o meno, puoi utilizzare questa estensione:

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

... ma ancora una volta, puoi facilmente modificare il codice sopra per adattarlo alle tue esigenze. Quindi supponiamo che la tua etichetta sia multilinea e abbia un'altezza variabile. Quindi l'estensione sarebbe simile a questa:

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

-6

Non sarebbe facile impostare l'attributo del titolo per l'etichetta, impostando questo verrà visualizzata l'etichetta completa quando si passa con il mouse.

puoi calcolare la lunghezza dell'etichetta e la larghezza del div (converti in lunghezza - jQuery / Javascript - Come faccio a convertire un valore di pixel (20px) in un valore numerico (20) ).

imposta jquery per impostare il titolo se la lunghezza è maggiore della larghezza del div.

var divlen = parseInt(jQuery("#yourdivid").width,10);
var lablen =jQuery("#yourlabelid").text().length;
if(lablen < divlen){
jQuery("#yourlabelid").attr("title",jQuery("#yourlabelid").text());
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.