如何检查UILabel是否被截断?

126

我有一个UILabel,其长度可能因iPhone或iPad上应用程序在纵向或横向模式下运行而异。当文本过长无法一行显示时,我希望用户能够按下它并获取弹出窗口中的完整文本。

如何检查UILabel是否截断了文本?这是否可行?目前,我仅基于所处模式检查不同的长度,但这并不是很有效。


1
请看我在这里所发布的基于行数统计的解决方案:链接 - Claus
2
难以置信的是,经过这么多年,苹果仍然没有将这样基本的功能纳入到UILabel API中。 - funct7
@funct7 - 这简直是疯狂。 - Fattie
23个回答

121
你可以计算字符串的宽度,并查看它是否大于label.bounds.size.width NSString UIKit Additions有几种方法可以使用特定字体计算字符串的大小。然而,如果你的标签设置了最小字体大小,则系统允许缩小文本至该大小。在这种情况下,你可能需要使用sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:
CGSize size = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
   ...
}

1
谢谢,这正是我所需要的。唯一的区别是,sizeWithFont:返回CGSize。 - Randall
啊,谢谢指出这个问题,我已经更正了示例代码。 - progrmr
16
sizeWithFont已经被弃用,请使用: [label.text sizeWithAttributes:@{NSFontAttributeName : label.font}]; - Amelia777
2
@fatuhoku numberOfLines 返回的是用于显示文本的最大行数,详见 UILabel 类的参考文档:https://developer.apple.com/library/ios/documentation/UIKit/Reference/UILabel_Class/index.html#//apple_ref/occ/instp/UILabel/numberOfLines - Paul
1
如果标签有多行,请尝试将宽度乘以行数,如下所示: if (size.width > label.bounds.size.width*label.numberOfLines) { ... } - Fadi Abuzant

120

Swift(作为扩展)- 适用于多行UILabel:

Swift 4:(boundingRectattributes参数稍有变化)

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
    }

}

将999999.0的高度替换为CGFloat(FLT_MAX) - ambientlight
2
对于Swift 3,我会使用CGFloat.greatestFiniteMagnitude。 - zero3nna
2
不错的回答,但最好使用计算属性而不是函数: 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, attributes: [NSFontAttributeName: self.font], context: nil).size return (size.height > self.bounds.size.height) } return false } - Mohammadalijf
1
这对我没有用,因为我正在使用NSAttributedString。为了使其工作,我需要修改属性值以使用:attributedText?.attributes(at: 0, effectiveRange: nil)。 - pulse4life
1
非常好的答案,我根据我的需求稍作修改,但完美地解决了问题。我还使用了一个属性字符串 - 所以对于属性,我使用了:(attributedText?.attributes(at: 0, effectiveRange: nil) ?? [.font: font]),只要确保在使用此解决方案时检查labelText不为空即可。 - Crt Gregoric
1
根据您检查此内容的时间,您可能需要在标签上调用layoutIfNeeded或在isTruncated代码内部调用它。 - user4713761

21

编辑:我刚看到我的答案被点赞了,但是我给出的代码片段已经过时了。
现在最好的方法是(ARC):


现在最好的方法是(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");
}

请注意,计算出来的大小并不是整数值。因此,如果您像这样执行操作int height = rect.size.height,您将失去一些浮点精度,并可能导致错误结果。

旧答案(已弃用):

如果您的标签有多行,可以使用以下代码:

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

18

看起来使用intrinsicContentSize可以完成设置了attributedTexttext的标签的工作。考虑到这一点,我认为我们可以安全地放弃所有边界框记录,并简化如下:

Swift 5.x

extension UILabel {
    var isTruncated: Bool {
       frame.width < intrinsicContentSize.width
    }

    var isClipped: Bool {
        frame.height < intrinsicContentSize.height
    }
}

16

Swift 3

在分配字符串后,您可以计算行数并将其与标签的最大行数进行比较。

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 {
        guard numberOfLines > 0 else { return false }
        return countLabelLines() > numberOfLines
    }
}

这是有关Swift的好答案。谢谢。 - JimmyLee

14

你可以使用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
从文档中翻译:textRectForBounds:limitedToNumberOfLines:“您不应直接调用此方法”... - Martin
@Martin 谢谢,我明白了,这里的实现有限制。但是当你在设置边界和文本后调用该方法时,它会正常工作。 - DongXu
在设置limitedToNumberOfLines时,为什么要+1? - Ash
@Ash 检查标签是否在为文本留更多空间时更高。 - vrwim
1
谢谢你提供这段代码,它对我很有帮助,除了在使用自动布局时出现的一些边界情况。我通过在方法开头添加以下代码来解决这些问题: setNeedsLayout() layoutIfNeeded() - Jovan Jovanovski

10

使用此类别查找iOS 7及以上版本中是否截断标签。

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

10

补充iDev的回答,应该使用intrinsicContentSize而不是frame,以使其适用于自动布局。

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

谢谢!在UILabel高度实际上足够适合文本,但它有限制的行数仍然会被截断时,使用intrinsicContentSize而不是frame是解决我的问题的方法。 - Anton Matosov
由于某些原因,即使文本不适合标签,这仍然返回“NO”。 - Can Poyrazoğlu

6

这就是它的作用。它可以与attributedText一起使用,如果无法使用,则退回到普通的text,对于那些处理多种字体族、大小甚至NSTextAttachments的人来说,这是很有意义的!

在自动布局中工作得很好,但显然必须在检查isTruncated之前定义和设置约束,否则标签本身甚至不知道如何布局,因此它甚至不知道是否被截断。

只使用简单的NSStringsizeThatFits方法并不能解决这个问题。我不确定人们是如何获得积极结果的。顺便说一下,如前所述,使用sizeThatFits根本不理想,因为它会考虑numberOfLines参数来计算结果大小,这违背了我们尝试做的事情,因为isTruncated将始终返回false,无论它是否被截断。

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

这对我来说很好用,谢谢!有没有更新或修改? - amodkanthe
很好的答案。唯一的问题是,即使您没有设置它,attributedText也永远不会为nil。因此,我将其实现为两个变量isTextTruncated和isAttributedTextTruncated。 - Elijah
@Harris,它在uilabel.h文件中说默认值为nil。 - Lucas Chwe
@LucasChwe 我知道,但实际上并不是这样的。你可以自己试一下看看。顺便说一句,https://forums.developer.apple.com/thread/118581 - Elijah

4

以下是Swift 3的选定答案(作为扩展)。 OP在询问1行标签。 我尝试过的许多Swift答案都特定于多行标签,并且不能正确地标记单行标签。

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

Axel的答案对这个问题没有起作用。这个解决方案有效。谢谢! - Glenn Posadas
intrinsicContentSize.width 对于文本和属性文本都适用吗? - Adrian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接