如何检查 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
将完成带有attributedText
和text
文本集的标签的工作。考虑到这一点,我认为我们可以安全地放弃所有边界框簿记并简化如下:
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
之前定义和设置约束,否则标签本身甚至不知道如何布局,所以它甚至不知道它是否被截断。
仅仅使用简单的NSString
和sizeThatFits
是无法解决这个问题的。我不确定人们是如何获得这样的积极结果的。顺便说一句,正如多次提到的那样,使用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]; @fatuhokunumberOfLines
返回用于显示文本的最大行数,如UILabel
类参考中所述:developer.apple.com/library/ios/documentation/UIKit/Reference/…
如果标签有行数尝试像这样将宽度乘以行数 if (size.width > label.bounds.size.width*label.numberOfLines) ... 【参考方案11】:
Swift(作为扩展) - 适用于多行 uilabel:
swift4:(boundingRect
的attributes
参数略有变化)
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 - 截断标签而不是按钮