web-dev-qa-db-fra.com

Comment vérifier si UILabel est tronqué?

J'ai un UILabel dont la longueur peut varier 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 apparaître sur une ligne et qu'il tronque, je veux que l'utilisateur puisse appuyer dessus et obtenir une fenêtre contextuelle du texte complet.

Comment puis-je vérifier si le UILabel tronque le texte? Est-ce même possible? En ce 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.

92
Randall

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

NSString UIKit Additions a 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 qui 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) {
   ...
}
102
progrmr

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

Swift4: (attributes param of boundingRect lé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
    }

}
77
Robin

EDIT: Je viens de voir que ma réponse a été votée positivement, mais l'extrait de code que j'ai donné est obsolète.
Maintenant, la meilleure façon de procéder 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 précision en virgule flottante et des résultats incorrects.

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

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;
}
13
DongXu

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
9
Rajesh

Pour ajouter à la réponse de iDev , vous devez utiliser intrinsicContentSize au lieu de frame, pour le faire fonctionner pour 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;
}
8
onmyway133

Swift

Vous pouvez compter le nombre de lignes après avoir attribué la chaîne et comparer au nombre maximal 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
    }
}
7
Claus

Ça y est. Cela fonctionne avec attributedText, avant de retomber sur text, ce qui est très logique pour nous qui traitons avec plusieurs familles de polices, tailles et même NSTextAttachments!

Fonctionne très 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 elle ne saurait même pas si elle est tronquée.

Il ne fonctionne pas pour aborder ce problème avec un simple NSString et sizeThatFits. Je ne sais pas comment les gens obtenaient des résultats positifs comme ça. BTW, comme mentionné à plusieurs reprises, l'utilisation de sizeThatFits n'est pas idéale du tout car elle prend en compte numberOfLines pour la taille résultante, ce qui va à l'encontre de l'objectif de ce que nous essayons de faire, parce que isTruncated renverrait toujours false, qu'il soit tronqué 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
    }
}
3
Lucas Chwe

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
2
Alejandro Cotilla

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

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

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

La solution que j'ai trouvée consiste à utiliser UIView.sizeThatFits :

extension UILabel {
    var isTruncated: Bool {
        layoutIfNeeded()
        let heightThatFits = sizeThatFits(bounds.size).height
        return heightThatFits > bounds.size.height
    }
}
1
Axel Guilmin
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.

1
Hayk Brsoyan

Voici la réponse sélectionnée dans Swift 3 (en tant qu'extension). L'OP demandait environ 1 étiquette de ligne. La plupart des réponses Swift que j'ai essayées ici sont spécifiques) sur les étiquettes multilignes et ne signalent pas correctement les étiquettes sur 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
    }
}
1
Travis M.

Pour ajouter à ce que @ iDev a fait, j'ai modifié le self.frame.size.height utiliser label.frame.size.height et n'a pas non plus utilisé NSStringDrawingUsesLineFontLeading. Après ces modifications, j'ai réalisé un calcul parfait du moment où la troncature se produirait (au 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;
}
1
kgaidis

Parce que toutes les réponses ci-dessus utilisent des méthodes obsolètes, 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;
}
0
Raz

Pour gérer iOS 6 (oui, certains d'entre nous doivent encore le faire), voici encore une autre extension de la réponse de @ iDev. La clé à retenir est que, pour iOS 6, pour vous assurer que numberOfLines de votre UILabel est défini sur 0 avant d'appeler sizeThatFits; sinon, il vous donnera un résultat qui dit que "les points pour dessiner la valeur de numberOfLines 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);
}
0
John Jacecko