如何检查 UILabel 是不是被截断?

Posted

技术标签:

【中文标题】如何检查 UILabel 是不是被截断?【英文标题】:How to check if UILabel is truncated?如何检查 UILabel 是否被截断? 【发布时间】:2011-03-05 20:41:04 【问题描述】:

我有一个UILabel,它的长度可能会有所不同,具体取决于我的应用是在 iPhone 还是 iPad 上以纵向还是横向模式运行。当文本太长而无法在一行上显示并且它被截断时,我希望用户能够按下它并弹出全文。

如何检查UILabel 是否截断了文本?甚至可能吗?现在我只是根据我所处的模式检查不同的长度,但效果不是很好。

【问题讨论】:

根据我发布的行数查看解决方案 here 不敢相信,这么多年过去了,Apple 仍然没有在UILabel API 中加入这样基本的东西。 【参考方案1】:

似乎intrinsicContentSize 将完成带有attributedTexttext 文本集的标签的工作。考虑到这一点,我认为我们可以安全地放弃所有边界框簿记并简化如下:

Swift 5.x

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

    var isClipped: Bool 
        frame.height < intrinsicContentSize.height
    

【讨论】:

【参考方案2】:

在 Swift 5.x 中

let size = label.text?.size(withAttributes: [NSAttributedString.Key.font: label.font!])
if size!.width > label.bounds.size.width 
    debugPrint("Size increased", size?.width ?? 0, label.bounds.size.width, label.text ?? "")

【讨论】:

【参考方案3】:

斯威夫特 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 的好答案。谢谢。【参考方案4】:

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 

    

【讨论】:

【参考方案5】:

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


【讨论】:

【参考方案6】:

就是这样。这适用于attributedText,然后又回到普通的text,这对于我们处理多种字体系列、大小甚至 NSTextAttachments 的人来说很有意义!

使用自动布局可以正常工作,但显然必须在我们检查 isTruncated 之前定义和设置约束,否则标签本身甚至不知道如何布局,所以它甚至不知道它是否被截断。

仅仅使用简单的NSStringsizeThatFits 是无法解决这个问题的。我不确定人们是如何获得这样的积极结果的。顺便说一句,正如多次提到的那样,使用sizeThatFits 一点也不理想,因为它考虑了numberOfLines 的结果大小,这违背了我们试图做的全部目的,因为isTruncated 总是会返回@ 987654330@不管它是否被截断。

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
    

【讨论】:

这对我来说很好,谢谢!!有任何更新或修改吗? 很好的答案。唯一的问题是 attributesText 永远不会为零,即使您没有设置它。因此,我将其实现为两个变量 isTextTruncated 和 isAttributedTextTruncated。 @Harris 说 uilabel.h 文件中的默认值为 nil @LucasChwe 我知道,但在实践中并非如此。你可以自己试试看。仅供参考,forums.developer.apple.com/thread/118581【参考方案7】:

在使用自动布局(设置最大高度)和带有NSParagraph.lineSpacing 的属性文本时,我遇到了boundingRect(with:options:attributes:context:) 的问题

忽略行间距(即使在 attributes 中传递给 boundingRect 方法时),因此标签可能被视为未截断。

我找到的解决方案是使用UIView.sizeThatFits

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

【讨论】:

【参考方案8】:
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
    

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

【讨论】:

【参考方案9】:

这是 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 的回答对此不起作用。这个做到了。谢谢! intrinsicContentSize.width 是否同时适用于文本和属性文本?【参考方案10】:

你可以计算width of the string,看看宽度是否大于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) 
   ...

【讨论】:

谢谢,这正是我所需要的。唯一的区别是,sizeWithFont: 返回一个 CGSize。 啊,谢谢指出,我已经更正了示例代码。 sizeWithFont 已弃用:[label.text sizeWithAttributes:@NSFontAttributeName : label.font]; @fatuhoku numberOfLines 返回用于显示文本的最大行数,如UILabel 类参考中所述:developer.apple.com/library/ios/documentation/UIKit/Reference/… 如果标签有行数尝试像这样将宽度乘以行数 if (size.width > label.bounds.size.width*label.numberOfLines) ... 【参考方案11】:

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

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


【讨论】:

对于 swift 3 我会使用 CGFloat.greatestFiniteMagnitude 不错的答案,但最好使用计算属性而不是 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, attributes: [NSFontAttributeName: self.font], context: nil).size return (size.height > self.bounds. size.height) 返回 false 这对我不起作用,因为我使用的是 NSAttributedString。为了让它工作,我需要修改要使用的属性值:attributeText?.attributes(at: 0, effectiveRange: nil) 很好的答案,必须根据我的要求对其进行一些更改,但效果很好。我还使用了属性字符串 - 所以对于我使用的属性:(attributedText?.attributes(at: 0, effectiveRange: nil) ?? [.font: font]),只需确保检查 labelText 是否不为空使用此解决方案。 您可能需要在标签上或在 isTruncated 的代码中调用 layoutIfNeeded,具体取决于您检查的时间【参考方案12】:

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
        
    

【讨论】:

【参考方案13】:

设置标签的title属性会不会很容易,设置它会在悬停时显示完整的标签。

您可以计算标签的长度和div的宽度(转换为长度-jQuery / javascript - How do I convert a pixel value (20px) to a number value (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());

【讨论】:

【参考方案14】:

编辑:我刚刚看到我的答案被赞成,但我给出的代码 sn-p 已被弃用。 现在最好的方法是(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");

【讨论】:

【参考方案15】:

我已经编写了一个用于处理 UILabel 截断的类别。适用于 iOS 7 及更高版本。希望能帮助到你 ! uilabel tail truncation

@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【参考方案16】:

要处理 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);

【讨论】:

【参考方案17】:

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

【讨论】:

【参考方案18】:

在 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

【讨论】:

【参考方案19】:

要添加到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 是我的问题的解决方案,但它的行数有限,因此仍在截断。 由于某种原因,即使文本不适合标签,这也会返回 NO【参考方案20】:

为了添加 @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;

【讨论】:

【参考方案21】:

因为上面的所有答案都使用了折旧的方法,我认为这可能很有用:

- (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ğlu 请测试一下。这是一个工作示例。 如果行数为零怎么办?【参考方案22】:

你可以用 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;

【讨论】:

来自文档:textRectForBounds:limitedToNumberOfLines:“你不应该直接调用这个方法”... @Martin 谢谢你,我明白了,这里的实现是有限的。但是当你在设置边界和文本后调用这个方法时,它会很好用 设置limitedToNumberOfLines时为什么要+1? @Ash 检查标签是否在允许更多文本空间时更高。 感谢这段代码,它对我有用,除了使用自动布局时的一些边界情况。我通过在方法的开头添加:setNeedsLayout()layoutIfNeeded() 来修复它们。

以上是关于如何检查 UILabel 是不是被截断?的主要内容,如果未能解决你的问题,请参考以下文章

swift iOS Swift:如何检查UILabel是否被截断?计算UILabel的行数

UILabel 和 UIButton - 截断标签而不是按钮

UITableViewCell - 如何使右 UILabel 截断而不是左截断?

如何找出截断的 UILabel 文本的宽度

UILabel 文本被截断

UiLabel 文本从顶部被截断