如何检查UILabel是否被截断?


105

我有一个UILabel依赖于我的应用程序是否被纵向或横向模式下运行在iPhone或iPad,可以长短不一。当文本太长而无法在一行上显示并且被截断时,我希望用户能够按它并弹出全文本。

如何检查,是否UILabel将文本截断?可能吗?现在,我只是根据自己所处的模式来检查不同的长度,但是它不能很好地工作。


1
看看基于我在此处
克劳斯(Claus)

这些年来,简直无法相信Apple仍未将如此基本的东西整合到UILabelAPI中。
funct7

Answers:


108

您可以计算字符串宽度,然后查看宽度是否大于label.bounds.size.width

NSString UIKit Additions具有几种用于计算具有特定字体的字符串大小的方法。但是,如果标签具有minimumFontSize,则允许系统将文本缩小到该大小。在这种情况下,您可能要使用sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:。

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

1
谢谢,这正是我所需要的。唯一的区别是sizeWithFont:返回CGSize。
兰德尔(Randall)2010年

嗯,感谢您指出,我已经更正了示例代码。
progrmr 2010年

16
不建议使用sizeWithFont:[label.text sizeWithAttributes:@ {NSFontAttributeName:label.font}];
Amelia777

2
@fatuhoku numberOfLines返回用于显示文本的最大行数,如UILabel类参考中所述:developer.apple.com/library/ios/documentation/UIKit/Reference/…–
Paul

1
如果label具有行数,请尝试将width与行数相乘,如果if(size.width> label.bounds.size.width * label.numberOfLines){...}
Fadi Abuzant

89

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

swift4 :(attributes参数boundingRect稍有变化)

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
    }

}

用CGFloat(FLT_MAX)替换999999.0的高度

2
为迅速3我将使用CGFloat.greatestFiniteMagnitude
zero3nna

2
好的答案,但是最好使用计算属性而不是func:var isTruncated:Bool {如果让字符串= self.text {让大小:CGSize =(字符串作为NSString) .width,height:CGFloat.greatestFiniteMagnitude),选项:NSStringDrawingOptions.usesLineFragmentOrigin,属性:[NSFontAttributeName:self.font],上下文:nil).size return(size.height> self.bounds.size.height)} return false}
Mohammadalijf

1
这对我不起作用,因为我正在使用NSAttributedString。为了使其正常工作,我需要修改属性值以使用:attributedText?.attributes(at:0,有效范围:nil)
pulse4life

1
很好的答案,必须根据我的要求进行一些更改,但效果很好。我也在使用属性字符串-因此,对于我使用的属性:(attributedText?.attributes(at:0,有效范围:nil)?? [.font:font]),只需确保在以下情况下检查labelText是否不为空使用此解决方案。
Crt Gregoric '18

20

编辑:我只是看到我的答案被否决,但我给出的代码段已被弃用。
现在最好的方法是(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");
}

13

您可以使用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谢谢,我知道这里的工具是有限的。但是当您在设置范围和文本后调用此方法时,它将很好用
DongXu 2012年

为什么在设置limitedToNumberOfLines时+1?
阿什2015年

@Ash,当允许为文本留出更多空间时,检查标签是否较高。
vrwim

1
感谢您提供的这段代码,它在使用自动布局时除了一些边界情况外,对我来说也很有效。我setNeedsLayout() layoutIfNeeded()在方法的开头添加了:
Jovan Jovanovski

9

使用此类别来查找标签在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

9

迅捷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 {

        if (self.countLabelLines() > self.numberOfLines) {
            return true
        }
        return false
    }
}

这是迅速的好答案。谢谢。
JimmyLee18年

8

要添加到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高度实际上足以容纳文本时,使用internalContentSize代替框架是解决我的问题的方法,但是它的行数有限,因此仍然被截断。
安东·马托索夫

由于某种原因,即使文本不适合标签,这也不会返回
CanPoyrazoğlu17年

6

就是这个。这与配合使用attributedText,然后又恢复text原状,这对于处理多个字体系列,大小甚至NSTextAttachments的我们人们来说非常有意义!

使用autolayout可以很好地工作,但是很显然,在我们检查之前必须先定义和设置约束isTruncated,否则标签本身甚至不知道如何进行布局,因此甚至无法知道其是否被截断。

它不工作,只是一个普通的来处理这个问题NSStringsizeThatFits。我不确定人们是如何获得积极成果的。正如许多次提到的那样,BTW 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永远不会为零,即使您没有设置它也是如此。因此,我将其实现为两个变量isTextTruncated和isAttributedTextTruncated。
哈里斯

@Harris表示uilabel.h文件中的默认值为nil
Lucas

我知道@LucasChwe,但实际上并非如此。您可以自己尝试一下,看看。fyi
哈里斯

3

这是Swift 3中的选定答案(作为扩展)。OP正在询问大约1个线路标签。我在这里尝试的许多快速答案都特定于多行标签,并且在单行标签上未正确标记。

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的答案对此无效。这个做了。谢谢!
格伦

2

这适用于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

我写了一个用于处理UILabel的截断的类别。适用于iOS 7及更高版本。希望能帮助到你 ! 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

每次仅提供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
    }
}

您可以计算字符串的宽度,然后查看宽度是否大于标签宽度。


1

为了增加@iDev所做的事情,我修改了self.frame.size.height使用label.frame.size.height,也没有使用NSStringDrawingUsesLineFontLeading。经过这些修改后,我获得了截断发生时间的完美计算(至少对于我而言)。

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

我有问题,boundingRect(with:options:attributes:context:)使用自动布局时(设置一个最大高度)和属性文本与NSParagraph.lineSpacing

行之间的间距被忽略(即使传递attributesboundingRect方法中也是如此),因此标签被认为不被截断。

我发现的解决方案是使用UIView.sizeThatFits

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

0

因为以上所有答案均使用折旧方法,所以我认为这可能很有用:

- (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),则表示标签被截断了。这是行不通的。
CanLeloğlu2015年

@CanLeloğlu,请对其进行测试。这是一个有效的例子。
拉兹2015年

2
如果numberOfLines等于零怎么办?
CanLeloğlu2015年

0

要处理iOS 6(是的,我们中有些人仍然必须这样做),这是@iDev答案的又一个扩展。关键要点是,对于iOS 6,在调用sizeThatFits之前确保UILabel的numberOfLines设置为0;如果不是,它会为您提供一个结果,提示“绘制标签文本需要用到绘制numberOfLines个高度的点”。

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

确保在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

设置为仅显示3行的多行UILabel的示例。

    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
    }

尽管用户可以选择返回键,而无需增加“文本宽度”,而是添加额外的一行,但在这种情况下,也可能会有用。

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

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

    }
}

-1

Swift 3解决方案

我认为最好的办法是(1)创建一个UILabel具有相同属性的标签,你的截断检查,(2)调用.sizeToFit()(3)与实际的标签比较假标签的属性。

例如,如果要检查宽度变化的单行标签是否被截断,则可以使用以下扩展名:

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

...但是同样,您可以轻松修改以上代码以适合您的需求。因此,假设您的标签是多行的并且具有不同的高度。然后,扩展名将如下所示:

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

设置标签的title属性是否容易,将其设置为悬停时将显示完整标签。

您可以计算标签的长度和div的宽度(转换为长度-jQuery / Javascript-如何将像素值(20px)转换为数字值(20))。

如果长度大于div宽度,则设置jquery以设置标题。

var divlen = parseInt(jQuery("#yourdivid").width,10);
var lablen =jQuery("#yourlabelid").text().length;
if(lablen < divlen){
jQuery("#yourlabelid").attr("title",jQuery("#yourlabelid").text());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.