Comment vérifier si UILabel est tronqué?

106

J'en ai une UILabelqui peut avoir des longueurs variables selon que mon application fonctionne ou non en mode portrait ou paysage sur un iPhone ou un iPad. Lorsque le texte est trop long pour s'afficher sur une ligne et qu'il tronque, je veux que l'utilisateur puisse appuyer dessus et obtenir une fenêtre contextuelle du texte intégral.

Comment puis-je vérifier si le UILabeltronque le texte? Est-ce même possible? Pour le moment, je vérifie simplement les différentes longueurs en fonction du mode dans lequel je suis, mais cela ne fonctionne pas très bien.

Randall
la source
1
Jetez un œil à la solution basée sur le nombre de lignes que j'ai posté ici
Claus
Je n'arrive pas à croire qu'après toutes ces années, Apple n'ait toujours pas intégré quelque chose d'aussi basique que celui-ci dans l' UILabelAPI.
funct7

Réponses:

108

Vous pouvez calculer la largeur de la chaîne et voir si la largeur est supérieure àlabel.bounds.size.width

NSString UIKit Additions dispose de plusieurs méthodes pour calculer la taille de la chaîne avec une police spécifique. Cependant, si vous avez un minimumFontSize pour votre étiquette, cela permet au système de réduire le texte à cette taille. Vous pouvez utiliser sizeWithFont: minFontSize: actualFontSize: forWidth: lineBreakMode: dans ce cas.

CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}
progrmr
la source
1
Merci, c'est exactement ce dont j'avais besoin. La seule différence était, sizeWithFont: renvoie un CGSize.
Randall
Ah, merci de l'avoir signalé, j'ai corrigé l'exemple de code.
progrmr le
16
sizeWithFont est déprécié use: [label.text sizeWithAttributes: @ {NSFontAttributeName: label.font}];
Amelia777
2
@fatuhoku numberOfLinesrenvoie le nombre maximum de lignes utilisées pour afficher le texte comme indiqué dans la UILabelréférence de classe: developer.apple.com/library/ios/documentation/UIKit/Reference/…
Paul
1
si l'étiquette a un nombre de lignes, essayez de multiplier la largeur par le nombre de lignes comme ceci if (size.width> label.bounds.size.width * label.numberOfLines) {...}
Fadi Abuzant
90

Swift (comme extension) - fonctionne pour uilabel multi-lignes:

swift4: (paramètre attributesde boundingRectlégèrement modifié)

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
la source
remplacer la hauteur de 999999.0 par CGFloat (FLT_MAX)
ambientlight
2
pour swift 3, j'utiliserais CGFloat.greatestFiniteMagnitude
zero3nna
2
belle réponse mais il vaut mieux utiliser la propriété calculée au lieu 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), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributs: [NSFontAttributeName: self.font], context: nil) .size return (size.height> self.bounds.size.height)} return false}
Mohammadalijf
1
Cela n'a pas fonctionné pour moi car j'utilisais un NSAttributedString. Pour le faire fonctionner, j'avais besoin de modifier la valeur des attributs à utiliser: AttributeText? .Attributes (at: 0, effectiveRange: nil)
pulse4life
1
Excellente réponse, j'ai dû le changer un peu pour mes besoins, mais a parfaitement fonctionné. J'utilisais également une chaîne attribuée - donc pour les attributs que j'ai utilisés: (AttributsText? .Attributes (at: 0, effectiveRange: nil) ?? [.font: font]), assurez-vous simplement de vérifier si le labelText n'est pas vide lorsque en utilisant cette solution.
Crt Gregoric
20

EDIT: Je viens de voir que ma réponse a été votée pour, mais l'extrait de code que j'ai donné est obsolète.
Maintenant, la meilleure façon de faire est (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");
}

Notez que la taille calculée n'est pas une valeur entière. Donc, si vous faites des choses comme int height = rect.size.height, vous perdrez une certaine précision en virgule flottante et pourriez avoir des résultats erronés.

Ancienne réponse (obsolète):

Si votre étiquette est multiligne, vous pouvez utiliser ce code:

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
la source
13

vous pouvez créer une catégorie avec 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
la source
3
de la doc: textRectForBounds:limitedToNumberOfLines:"Vous ne devriez pas appeler cette méthode directement" ...
Martin
@Martin merci, je vois ça, l'outil ici est limité, mais quand vous appelez cette méthode après avoir défini les limites et le texte, cela fonctionnera bien
DongXu
Pourquoi attribuez-vous +1 lors de la définition des limitedToNumberOfLines?
Ash
@Ash pour vérifier si le libellé est plus haut quand il est autorisé plus de place pour le texte.
vrwim
1
Merci pour ce code, cela a fonctionné pour moi en dehors de certains cas de bordure lors de l'utilisation de la mise en page automatique. Je les ai corrigés en ajoutant: setNeedsLayout() layoutIfNeeded()au début de la méthode.
Jovan Jovanovski
9

Utilisez cette catégorie pour savoir si une étiquette est tronquée sur iOS 7 et supérieur.

// 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
la source
9

Swift 3

Vous pouvez compter le nombre de lignes après avoir attribué la chaîne et comparer au nombre maximum de lignes de l'étiquette.

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
la source
C'est la bonne réponse pour swift. Merci.
JimmyLee
8

Pour ajouter à la réponse d' iDev , vous devez utiliser à la intrinsicContentSizeplace de frame, pour que cela fonctionne pour la mise en page automatique

- (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
la source
Merci! L'utilisation de intrinsicContentSize au lieu de frame était la solution à mon problème lorsque la hauteur d'UILabel est en fait suffisante pour s'adapter au texte, mais il a un nombre limité de lignes et est donc toujours tronqué.
Anton Matosov
Pour une raison quelconque, cela renvoie NON même lorsque le texte ne rentre pas dans l'étiquette
Can Poyrazoğlu
6

Ça y est. Cela fonctionne avec attributedText, avant de retomber dans la plaine text, ce qui a beaucoup de sens pour nous, gens qui traitons plusieurs familles de polices, tailles et même NSTextAttachments!

Fonctionne bien avec la mise en page automatique, mais de toute évidence, les contraintes doivent être définies et définies avant de vérifier isTruncated, sinon l'étiquette elle-même ne saura même pas comment se présenter, donc il ne saurait même pas si elle est tronquée.

Il ne fonctionne pas d'aborder ce problème avec juste un simple NSStringet sizeThatFits. Je ne sais pas comment les gens obtenaient des résultats positifs comme ça. BTW, comme mentionné à de nombreuses reprises, l'utilisation sizeThatFitsn'est pas du tout idéale car elle prend en compte numberOfLinesla taille résultante, ce qui va à l'encontre de l'objectif de ce que nous essayons de faire, car elle isTruncatedreviendrait toujours, qu'elle falsesoit tronquée ou non.

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
la source
cela fonctionne bien pour moi merci !! y a-t-il des mises à jour ou des modifications pour cela?
amodkanthe
Très bonne réponse. Le seul problème est que le texte attribué n'est jamais nul, même si vous ne le définissez pas. Donc, j'ai implémenté cela en tant que deux vars isTextTruncated et isAttributedTextTruncated.
Harris
@Harris, il dit que la valeur par défaut est nulle dans le fichier uilabel.h
Lucas
@LucasChwe Je sais, mais ce n'est pas vrai dans la pratique. Vous pouvez l'essayer par vous-même et voir. fyi, forums.developer.apple.com/thread/118581
Harris
4

Voici la réponse sélectionnée dans Swift 3 (en tant qu'extension). L'OP demandait environ 1 ligne d'étiquettes. La plupart des réponses rapides que j'ai essayées ici sont spécifiques aux étiquettes multilignes et ne signalent pas correctement les étiquettes à une seule ligne.

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.
la source
La réponse d'Axel n'a pas fonctionné pour cela. Celui-ci l'a fait. Merci!
Glenn
2

Cela fonctionne pour 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
la source
2

J'ai écrit une catégorie pour travailler avec la troncature d'UILabel. Fonctionne sur iOS 7 et versions ultérieures. J'espère que ça aide ! troncature de queue 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
la source
chaque fois qu'il donne NSNotFound uniquement
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
    }
}

Vous pouvez calculer la largeur de la chaîne et voir si la largeur est supérieure à la largeur de l'étiquette.

Hayk Brsoyan
la source
1

Pour ajouter à ce que @iDev a fait, j'ai modifié le self.frame.size.heightpour utiliser label.frame.size.heightet je ne l'ai pas non plus utilisé NSStringDrawingUsesLineFontLeading. Après ces modifications, j'ai réalisé un calcul parfait du moment où la troncature se produirait (du moins pour mon cas).

- (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
la source
1

J'ai eu des problèmes boundingRect(with:options:attributes:context:)lors de l'utilisation de la mise en page automatique (pour définir une hauteur maximale) et d'un texte attribué avecNSParagraph.lineSpacing

L'espacement entre les lignes était ignoré (même lorsqu'il était transmis attributesà la boundingRectméthode), de sorte que l'étiquette pouvait être considérée comme non tronquée lorsqu'elle l'était.

La solution que j'ai trouvée est d'utiliser UIView.sizeThatFits:

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

Parce que toutes les réponses ci-dessus utilisent des méthodes dépréciées, j'ai pensé que cela pourrait être 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;
}
Raz
la source
Vous divisez la largeur par la hauteur et si elle est plus grande que le nombre de lignes (qui pourrait très bien être 0), vous dites que l'étiquette est tronquée. Il n'y a aucun moyen que cela fonctionne.
Can Leloğlu
@ CanLeloğlu, veuillez le tester. C'est un exemple concret.
Raz
2
Que faire si numberOfLines est égal à zéro?
Can Leloğlu
0

Pour gérer iOS 6 (oui, certains d'entre nous doivent encore le faire), voici une autre extension de la réponse de @ iDev. La clé à retenir est que, pour iOS 6, assurez-vous que numberOfLines de votre UILabel est défini sur 0 avant d'appeler sizeThatFits; sinon, cela vous donnera un résultat qui dit que "les points pour dessiner numberOfLines d'une valeur de hauteur" sont nécessaires pour dessiner le texte de l'étiquette.

- (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
la source
0

Assurez-vous d'appeler l'un de ces éléments dans 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
la source
0

SWIFT 5

Exemple pour un UILabel à lignes multiples configuré pour n'afficher que 3 lignes.

    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
    }

Bien que l'utilisateur puisse sélectionner la touche de retour en ajoutant une ligne supplémentaire sans ajouter à la "largeur du texte", dans ce cas, quelque chose comme cela peut également être utile.

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

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

    }
}
Waylan Sands
la source
-1

Solution Swift 3

Je pense que la meilleure solution est de (1) créer un UILabelavec les mêmes propriétés que l'étiquette que vous vérifiez pour la troncature, (2) appeler .sizeToFit(), (3) comparer les attributs de l'étiquette factice avec votre étiquette réelle.

Par exemple, si vous souhaitez vérifier si une étiquette à une ligne qui a une largeur variable tronque ou non, vous pouvez utiliser cette extension:

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

... mais encore une fois, vous pouvez facilement modifier le code ci-dessus en fonction de vos besoins. Disons que votre étiquette est multiligne et a une hauteur variable. Ensuite, l'extension ressemblerait à ceci:

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
la source
-6

Ne serait-il pas facile de définir l'attribut title pour le libellé, le paramétrer affichera le libellé complet lors du survol.

vous pouvez calculer la longueur de l'étiquette et la largeur div (convertir en longueur - jQuery / Javascript - Comment puis-je convertir une valeur de pixel (20px) en une valeur numérique (20) ).

set jquery pour définir le titre si la longueur est supérieure à la largeur 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
la source