在 UILabel 末尾添加“...阅读更多”

Posted

技术标签:

【中文标题】在 UILabel 末尾添加“...阅读更多”【英文标题】:Add "...Read More" to the end of UILabel 【发布时间】:2015-08-31 10:33:22 【问题描述】:

我有一个UILabel,在某些情况下,文本比UILabel 本身更长,所以我将文本视为"bla bla bla..." 我想在@987654325 末尾添加一个...Read More 按钮文本@..

我读过一些帖子,但它们提供了对我不利的解决方案,例如:计算有多少字符将输入UILabel,但我使用的字体每个字符都有不同的宽度。

我怎样才能做到这一点?

提前致谢!

【问题讨论】:

使用 truncateinMiddle 【参考方案1】:

Swift4 (IOS 11.2)

Readmore在标签的末尾没有操作

extension UILabel 

    func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) 
        let readMoreText: String = trailingText + moreText

        let lengthForVisibleString: Int = self.visibleTextLength
        let mutableString: String = self.text!
        let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: ((self.text?.count)! - lengthForVisibleString)), with: "")
        let readMoreLength: Int = (readMoreText.count)
        let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText
        let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSAttributedStringKey.font: self.font])
        let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedStringKey.font: moreTextFont, NSAttributedStringKey.foregroundColor: moreTextColor])
        answerAttributed.append(readMoreAttributed)
        self.attributedText = answerAttributed
    

    var visibleTextLength: Int 
        let font: UIFont = self.font
        let mode: NSLineBreakMode = self.lineBreakMode
        let labelWidth: CGFloat = self.frame.size.width
        let labelHeight: CGFloat = self.frame.size.height
        let sizeConstraint = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)

        let attributes: [AnyHashable: Any] = [NSAttributedStringKey.font: font]
        let attributedText = NSAttributedString(string: self.text!, attributes: attributes as? [NSAttributedStringKey : Any])
        let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)

        if boundingRect.size.height > labelHeight 
            var index: Int = 0
            var prev: Int = 0
            let characterSet = CharacterSet.whitespacesAndNewlines
            repeat 
                prev = index
                if mode == NSLineBreakMode.byCharWrapping 
                    index += 1
                 else 
                    index = (self.text! as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: self.text!.count - index - 1)).location
                
             while index != NSNotFound && index < self.text!.count && (self.text! as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedStringKey : Any], context: nil).size.height <= labelHeight
            return prev
        
        return self.text!.count
    

Swift 4.2

extension UILabel 

        func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) 
            let readMoreText: String = trailingText + moreText

            let lengthForVisibleString: Int = self.vissibleTextLength
            let mutableString: String = self.text!
            let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: ((self.text?.count)! - lengthForVisibleString)), with: "")
            let readMoreLength: Int = (readMoreText.count)
            let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText
            let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSAttributedString.Key.font: self.font])
            let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedString.Key.font: moreTextFont, NSAttributedString.Key.foregroundColor: moreTextColor])
            answerAttributed.append(readMoreAttributed)
            self.attributedText = answerAttributed
        

        var vissibleTextLength: Int 
            let font: UIFont = self.font
            let mode: NSLineBreakMode = self.lineBreakMode
            let labelWidth: CGFloat = self.frame.size.width
            let labelHeight: CGFloat = self.frame.size.height
            let sizeConstraint = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)

            let attributes: [AnyHashable: Any] = [NSAttributedString.Key.font: font]
            let attributedText = NSAttributedString(string: self.text!, attributes: attributes as? [NSAttributedString.Key : Any])
            let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)

            if boundingRect.size.height > labelHeight 
                var index: Int = 0
                var prev: Int = 0
                let characterSet = CharacterSet.whitespacesAndNewlines
                repeat 
                    prev = index
                    if mode == NSLineBreakMode.byCharWrapping 
                        index += 1
                     else 
                        index = (self.text! as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: self.text!.count - index - 1)).location
                    
                 while index != NSNotFound && index < self.text!.count && (self.text! as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedString.Key : Any], context: nil).size.height <= labelHeight
                return prev
            
            return self.text!.count
        
    

用法

let readmoreFont = UIFont(name: "Helvetica-Oblique", size: 11.0)
let readmoreFontColor = UIColor.blue
DispatchQueue.main.async 
    self.yourLabel.addTrailing(with: "... ", moreText: "Readmore", moreTextFont: readmoreFont!, moreTextColor: readmoreFontColor)

结果

注意:- Readmore 不包含操作

【讨论】:

如何添加动作? @JesseOnolememen 您可以在标签上添加也有效的操作 我收到无法转换类型“[String : Any]?”的值到预期的参数类型“[NSAttributedStringKey:Any]?”使用 Swift 4 小心!只要字符串有一定的大小,它就可以工作,否则它会崩溃。例如,如果文本是“经理”,它不会崩溃,但如果文本是“经理”,它会崩溃。如果文本是“abcdefg”或“.......”(那里有 7 个点),它也会崩溃。每个应用程序都不同,对于我的应用程序,我让用户输入 3 到 100 个字符之间的任何文本。我使用的字体是 UIFont(name: "ArialRoundedMTRegular", size: 12)。如果您使用不同的字体,请测试字符限制,并使用简单的单音节单词进行测试,看看是否会崩溃。除此之外,这工作正常。 问题是如果文本字符的数量小于附加文本就会崩溃。例如,我没有使用“... Readmore”,而是使用了“... more”,即 8 个字符,包括空格。 “manager”和“abcdefg”这个词是 7 个字符,少于 8 个,所以它崩溃了。我将附加文本更改为“...”,它只有 3 个字符,而且效果很好。基本上标签文本中的字符数必须 >= with: 和 moreText: 参数中的字符数。【参考方案2】:

这就是我将 阅读更多... 按钮添加到 UITextViewUITextFieldUILabel 的操作:

- (void)addReadMoreStringToUILabel:(UILabel*)label

    NSString *readMoreText = @" ...Read More";
    NSInteger lengthForString = label.text.length;
    if (lengthForString >= 30)
    
        NSInteger lengthForVisibleString = [self fitString:label.text intoLabel:label];
        NSMutableString *mutableString = [[NSMutableString alloc] initWithString:label.text];
        NSString *trimmedString = [mutableString stringByReplacingCharactersInRange:NSMakeRange(lengthForVisibleString, (label.text.length - lengthForVisibleString)) withString:@""];
        NSInteger readMoreLength = readMoreText.length;
        NSString *trimmedForReadMore = [trimmedString stringByReplacingCharactersInRange:NSMakeRange((trimmedString.length - readMoreLength), readMoreLength) withString:@""];
        NSMutableAttributedString *answerAttributed = [[NSMutableAttributedString alloc] initWithString:trimmedForReadMore attributes:@
                                                                                                                                        NSFontAttributeName : label.font
                                                                                                                                        ];

        NSMutableAttributedString *readMoreAttributed = [[NSMutableAttributedString alloc] initWithString:readMoreText attributes:@
                                                                                                                                        NSFontAttributeName : Font(TWRegular, 12.),
                                                                                                                                        NSForegroundColorAttributeName : White
                                                                                                                                        ];

        [answerAttributed appendAttributedString:readMoreAttributed];
        label.attributedText = answerAttributed;

        UITagTapGestureRecognizer *readMoreGesture = [[UITagTapGestureRecognizer alloc] initWithTarget:self action:@selector(readMoreDidClickedGesture:)];
        readMoreGesture.tag = 1;
        readMoreGesture.numberOfTapsRequired = 1;
        [label addGestureRecognizer:readMoreGesture];

        label.userInteractionEnabled = YES;
    
    else 

        NSLog(@"No need for 'Read More'...");

    

有一个使用fitString:intoLabel的方法可以在here找到。

至于UITagTapGestureRecognizer 只是一个普通的UITapGestureRecognizer 子类,带有一个称为标签的NSInteger 属性。我这样做是因为我想确定点击了哪些Read More...,以防万一我在同一个UIViewController 中有多个。你可以使用普通的UITapGestureRecognizer

享受吧!

【讨论】:

点击标签时如何增加标签高度 请给我这个答案的快速版本。 点击手势将作用于整个标签,而不仅仅是阅读更多文本。 我能得到这个函数的swift版本吗 Swift 版本好吗??【参考方案3】:

Tttattributed标签有这个功能

https://github.com/TTTAttributedLabel/TTTAttributedLabel

您需要将“截断”标记设置为 “阅读更多……”

attributedTruncationToken

var subTitleLabel = TTTAttributedLabel(frame : frame)
    self.addSubview(subTitleLabel)
    var trunc = NSMutableAttributedString(string: "...more")
    trunc.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(12), range: NSMakeRange(0, 7))
    trunc.addAttribute(NSForegroundColorAttributeName, value: UIColor.blueColor(), range: NSMakeRange(0, 7))
    subTitleLabel.attributedTruncationToken = trunc
    subTitleLabel.numberOfLines = 1
    subTitleLabel.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth

【讨论】:

【参考方案4】:

这适用于 Swift 5

这是@ramchandran 答案的更安全版本,因为您不知道用户将输入多少个字符。

在他的回答中,如果用户输入的字符串长度小于您决定用于... Readmore 的任何文本的长度,那么它将崩溃。例如,这就是你使用它的方式

if yourLabel.text!.count > 1 

   let readmoreFont = UIFont(name: "Helvetica-Oblique", size: 11.0)
    let readmoreFontColor = UIColor.blue
    DispatchQueue.main.async 
        self.yourLabel.addTrailing(with: "... ", moreText: "Readmore", moreTextFont: readmoreFont!, moreTextColor: readmoreFontColor)
    

在上面的例子中,... Readmore 的输出总共是 12 个字符。如果用户输入的字符串是yourLabel.text = "12345678",那么字符串的文本将只有 8 个字符。 它会崩溃,因为在下面的行中使用((trimmedString?.count ?? 0) - readMoreLength) 的范围会产生否定结果:

// “12345678” minus “... Readmore” = negative four (8 - 12 = -4)
let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText

我添加了一个安全检查,以确保如果输入的字符串小于或等于您决定用作 ... Readmore 的字符数,它将返回并且将导致崩溃的行永远不会出现到达:

// trimmedString is the string the user entered
guard let safeTrimmedString = trimmedString else  return 
if safeTrimmedString.count <= readMoreLength  return 

它位于addTrailing函数的中心

extension UILabel    
    
    func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) 
        
        let readMoreText: String = trailingText + moreText
        
        if self.visibleTextLength == 0  return 
        
        let lengthForVisibleString: Int = self.visibleTextLength
        
        if let myText = self.text 
            
            let mutableString: String = myText
            
            let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: myText.count - lengthForVisibleString), with: "")
            
            let readMoreLength: Int = (readMoreText.count)
            
            guard let safeTrimmedString = trimmedString else  return 
            
            if safeTrimmedString.count <= readMoreLength  return 
            
            print("this number \(safeTrimmedString.count) should never be less\n")
            print("then this number \(readMoreLength)")
            
            // "safeTrimmedString.count - readMoreLength" should never be less then the readMoreLength because it'll be a negative value and will crash
            let trimmedForReadMore: String = (safeTrimmedString as NSString).replacingCharacters(in: NSRange(location: safeTrimmedString.count - readMoreLength, length: readMoreLength), with: "") + trailingText
            
            let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSAttributedString.Key.font: self.font])
            let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedString.Key.font: moreTextFont, NSAttributedString.Key.foregroundColor: moreTextColor])
            answerAttributed.append(readMoreAttributed)
            self.attributedText = answerAttributed
        
    
    
    var visibleTextLength: Int 
        
        let font: UIFont = self.font
        let mode: NSLineBreakMode = self.lineBreakMode
        let labelWidth: CGFloat = self.frame.size.width
        let labelHeight: CGFloat = self.frame.size.height
        let sizeConstraint = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)
        
        if let myText = self.text 
            
            let attributes: [AnyHashable: Any] = [NSAttributedString.Key.font: font]
            let attributedText = NSAttributedString(string: myText, attributes: attributes as? [NSAttributedString.Key : Any])
            let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)
            
            if boundingRect.size.height > labelHeight 
                var index: Int = 0
                var prev: Int = 0
                let characterSet = CharacterSet.whitespacesAndNewlines
                repeat 
                    prev = index
                    if mode == NSLineBreakMode.byCharWrapping 
                        index += 1
                     else 
                        index = (myText as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: myText.count - index - 1)).location
                    
                 while index != NSNotFound && index < myText.count && (myText as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedString.Key : Any], context: nil).size.height <= labelHeight
                return prev
            
        
        
        if self.text == nil 
            return 0
         else 
            return self.text!.count
        
    

【讨论】:

感谢 Lance 让它变得更好。我正在使用此代码。它没有显示标签中的文本。我没有做任何修改,我就照原样使用它。 让 readmoreFont = UIFont(name: "Helvetica-Oblique", size: 14.0) let readmoreFontColor = UIColor.blue DispatchQueue.main.async self.movi​​eDescLbl.addTrailing(with: self.movi​​eDesc, moreText : "Readmore", moreTextFont: readmoreFont!, moreTextColor: readmoreFontColor) 让我们continue this discussion in chat。 self.movi​​eDescLbl.addTrailing(with: self.movi​​eDesc, moreText: "Readmore", moreTextFont: readmoreFont, moreTextColor: readmoreFontColor)。这里 self.movi​​eDesc 是一个包含 40 多个字符的字符串。所以我做的是对还是错? @Raxit Pandya 我看到你的问题,你做错了,看看你写的人,这是一个简单的错误。问题在这里 addTrailing(with: self.movi​​eDesc,它应该是 addTrailing(with: "..." 你应该在那里添加 3 个点【参考方案5】:

Swift 4 和 Swift 5。我需要实现相同的。由于已经给出了答案,但据我所知 TTTAttributedLabel 是最好的方法。它使您可以更好地控制内容。易于查找地址、链接、日期等。您还可以更改链接的颜色。上面的答案中已经给出了 TTTAttributedLabel 库链接。让我们开始实施吧。

let kCharacterBeforReadMore =  20
let kReadMoreText           =  "...ReadMore"
let kReadLessText           =  "...ReadLess"

@IBOutlet weak var labelText: TTTAttributedLabel! // setYouLabel Class to TTTAttributedLabel in StoryBoard
var strFull = ""

 override func viewDidLoad() 
      super.viewDidLoad()
      strFull = "I need to implement the same. As answers are already given but according to me TTTAttributedLabel is the best way to do it. I gives I need to implement the same. As answers are already given but according to me TTTAttributedLabel is the best way to do it. I gives you"
      labelText.showTextOnTTTAttributeLable(str: strFull, readMoreText: kReadMoreText, readLessText: kReadLessText, font: UIFont.init(name: "Helvetica-Bold", size: 24.0)!, charatersBeforeReadMore: kCharacterBeforReadMore, activeLinkColor: UIColor.blue, isReadMoreTapped: false, isReadLessTapped: false)
      labelText.delegate = self
   
      func readMore(readMore: Bool) 
        labelText.showTextOnTTTAttributeLable(str: strFull, readMoreText: kReadMoreText, readLessText: kReadLessText, font: nil, charatersBeforeReadMore: kCharacterBeforReadMore, activeLinkColor: UIColor.blue, isReadMoreTapped: readMore, isReadLessTapped: false)
      
      func readLess(readLess: Bool) 
        labelText.showTextOnTTTAttributeLable(str: strFull, readMoreText: kReadMoreText, readLessText: kReadLessText, font: nil, charatersBeforeReadMore: kCharacterBeforReadMore, activeLinkColor: UIColor.blue, isReadMoreTapped: readLess, isReadLessTapped: true)
      

在这里,我创建了 TTTAttributedLabel 的扩展并将 ReadMore 和 ReadLess 逻辑放在这里。可以根据自己的情况进行修改。

 extension TTTAttributedLabel 
      func showTextOnTTTAttributeLable(str: String, readMoreText: String, readLessText: String, font: UIFont?, charatersBeforeReadMore: Int, activeLinkColor: UIColor, isReadMoreTapped: Bool, isReadLessTapped: Bool) 

        let text = str + readLessText
        let attributedFullText = NSMutableAttributedString.init(string: text)
        let rangeLess = NSString(string: text).range(of: readLessText, options: String.CompareOptions.caseInsensitive)
//Swift 5
       // attributedFullText.addAttributes([NSAttributedStringKey.foregroundColor : UIColor.blue], range: rangeLess)
        attributedFullText.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.blue], range: rangeLess)

        var subStringWithReadMore = ""
        if text.count > charatersBeforeReadMore 
          let start = String.Index(encodedOffset: 0)
          let end = String.Index(encodedOffset: charatersBeforeReadMore)
          subStringWithReadMore = String(text[start..<end]) + readMoreText
        

        let attributedLessText = NSMutableAttributedString.init(string: subStringWithReadMore)
        let nsRange = NSString(string: subStringWithReadMore).range(of: readMoreText, options: String.CompareOptions.caseInsensitive)
        //Swift 5
       // attributedLessText.addAttributes([NSAttributedStringKey.foregroundColor : UIColor.blue], range: nsRange)
        attributedLessText.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.blue], range: nsRange)
      //  if let _ = font // set font to attributes
      //   self.font = font
      //  
        self.attributedText = attributedLessText
        self.activeLinkAttributes = [NSAttributedString.Key.foregroundColor : UIColor.blue]
        //Swift 5
       // self.linkAttributes = [NSAttributedStringKey.foregroundColor : UIColor.blue]
        self.linkAttributes = [NSAttributedString.Key.foregroundColor : UIColor.blue]
        self.addLink(toTransitInformation: ["ReadMore":"1"], with: nsRange)

        if isReadMoreTapped 
          self.numberOfLines = 0
          self.attributedText = attributedFullText
          self.addLink(toTransitInformation: ["ReadLess": "1"], with: rangeLess)
        
        if isReadLessTapped 
          self.numberOfLines = 3
          self.attributedText = attributedLessText
        
      
    

您需要实现 TTTAttributedLabel 的 didSelectLinkWithTransitInformation 委托。在这里你可以得到你通过的组件

extension ViewController: TTTAttributedLabelDelegate 
  func attributedLabel(_ label: TTTAttributedLabel!, didSelectLinkWithTransitInformation components: [AnyHashable : Any]!) 
    if let _ = components as? [String: String] 
      if let value = components["ReadMore"] as? String, value == "1" 
        self.readMore(readMore: true)
      
      if let value = components["ReadLess"] as? String, value == "1" 
        self.readLess(readLess: true)
      
    
  

结果 - 在点击阅读更多之前

结果-点击阅读后

【讨论】:

感谢您的完整回答,除了这部分之外,它对我有用:if let _ = font self.font = font 因为我们将文本分配给标签的 `attributedText' 属性,我们需要将字体作为属性添加到字符串 我刚刚根据我的要求进行了更新,它的工作就像魅力一样。谢谢 谢谢兄弟,这正是我要找的东西 @MohyG,你是如何设法在 showTextOnTTTAttributeLable 函数中设置字体的?我已经尝试在属性中设置字体,但除了 Read More 和 Read Less 之外,整个文本都显示为黑色并且字体大小非常小。任何帮助将不胜感激! 参考这个gist.github.com/Catherine-K-George/…【参考方案6】:
class DynamicLabel: UILabel

    var fullText: String?
    var truncatedLength = 100
    var isTruncated = true

    func collapse()
        let index = fullText!.index(fullText!.startIndex, offsetBy: truncatedLength)
        self.text = fullText![...index].description + "... More"
        isTruncated = true
    

    func expand()
        self.text = fullText
        isTruncated = false
    


只是一个简单的技巧来解决所有这些混乱的实现。这个想法很简单,我们不设置折叠或展开行,只需将标签设置为 0。 然后将原始文本存储在fullText 变量中。现在如果我们想显示折叠格式,那么只需获取子字符串并添加自定义省略号。

注意:这不包括点击事件处理程序,您可以在控制器上自己添加。

【讨论】:

【参考方案7】:

使用- boundingRectWithSize:options:attributes:context: 方法并将您的字体作为NSFontAttributeName 键传递给NSAttributedString 将为您提供所需的正确矩形。

您需要检查它是否大于您的标签边界减去偏移量。只有在是时,您才需要修剪文本并在末尾显示Read More

【讨论】:

我已经计算了原始文本的边界矩形(作为 NSAttributedString),接下来我该怎么做?我怎样才能检查有多少字符适合这个大小并主干文本并使用“...阅读更多”文本对其进行子串?【参考方案8】:

你可以试试第三库ExpandableLable

将 UILabel 的自定义类设置为 ExpandableLabel 并设置所需的行数和折叠文本:

expandableLabel.numberOfLines = 5
expandableLabel.collapsedAttributedLink = NSAttributedString(string: "more")
expandableLabel.ellipsis = NSAttributedString(string: "...")
// update label expand or collapse state
expandableLabel.collapsed = true

您可能需要设置delegate 以在链接被触摸时收到通知。

【讨论】:

仅适用于系统字体,不适用于自定义字体。 实际上我使用的是自定义字体、粗细和大小。它工作得很好,你只需要在 NSAttributedString(string:attributes:) 函数中使用它。 我对这个库有疑问,在 tableview 中“显示更多”在滚动后第一次加载时没有显示【参考方案9】:

我的解决方案是,我在UILabel 的右下角和下方创建一个UIButton(名称阅读更多)。 之后,我检查 UILabel 是否被截断以显示或隐藏 UIButton

CGSize sizeOfText = [self.label.text boundingRectWithSize: CGSizeMake(self.label.intrinsicContentSize.width, CGFLOAT_MAX)
                                                 options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                              attributes: [NSDictionary dictionaryWithObject:self.label.font forKey:NSFontAttributeName] context: nil].size;
    
if (self.label.intrinsicContentSize.height < ceilf(sizeOfText.height)) 
    // label is truncated
    self.readmoreButton.hidden = NO; // show Read more button
else
    self.readmoreButton.hidden = YES;

=== Swift 3 版本

let textheight = self.label.text?.height(withConstrainedWidth: self.label.frame.width, font: self.label.font)
    if self.label.intrinsicContentSize.height < textheight! 
        self.readmoreButton.isHidden = false
    else
        self.readmoreButton.isHidden = true
    

添加这个扩展:

extension String 

func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat 
    let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
    let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
    
    return boundingBox.height
 

希望有帮助

【讨论】:

这仅在您希望 UILabel 下方而不是旁边的“阅读更多”选项时才有效,对吧?【参考方案10】:

此方法对于 showless 和 showAll with updown 箭头图像很有用: 在标签上添加手势

  viewcontroller.h

  @property (nonatomic,assign) BOOL isReadable; 

  viewcontrollr.m

  #pragma mark :- Tap Gesture View All
  -(void)readMoreDidClickedGesture :(UITapGestureRecognizer 
    *)objTapGesture

     UILabel * lblDesc = (UILabel *)[objTapGesture view];
     NSLog(@"%@",lblDesc.text);
     if (self.isReadable == false) 
     [self setIsReadable:YES];
     lblDesc.text = readmoreText;
     readMoreHeight = [self getLabelHeight:lblDesc];
     
     else
      readMoreHeight = 30.0;
      [self setIsReadable:NO];
        
  



 - (void)addReadMoreStringToUILabel:(UILabel*)label isReaded:(BOOL)isReaded
 

  NSString *readMoreText = (isReaded == false) ? @"...Show All  " : 
   @"Show Less  ";
  NSInteger lengthForString = label.text.length;
  if (lengthForString >= 30)
  
    NSInteger lengthForVisibleString = [self getLabelHeight:label];//[self fitString:label.text intoLabel:label];
    NSMutableString *mutableString = [[NSMutableString alloc] initWithString:label.text];
    readmoreText = mutableString;
    NSString *trimmedString = [mutableString stringByReplacingCharactersInRange:NSMakeRange(lengthForVisibleString, (label.text.length - lengthForVisibleString)) withString:@""];
    NSInteger readMoreLength = readMoreText.length;
    NSString *trimmedForReadMore = [trimmedString stringByReplacingCharactersInRange:NSMakeRange((trimmedString.length - readMoreLength), readMoreLength) withString:@""];
    NSMutableAttributedString *answerAttributed = [[NSMutableAttributedString alloc] initWithString:trimmedForReadMore attributes:@
                                                                                                                                    NSFontAttributeName : label.font
                                                                                                                                    ];


    NSMutableAttributedString *readMoreAttributed = [[NSMutableAttributedString alloc] initWithString:readMoreText attributes:@
                                                                                                                                NSFontAttributeName :label.font,                              NSForegroundColorAttributeName :[UIColor orangeColor]
                                                                                                                                ];
    if (isReaded == false)
        [readMoreAttributed addAttribute:NSUnderlineStyleAttributeName
                             value:@(NSUnderlineStyleSingle)
                             range:NSMakeRange(3, 8)];

        NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
        UIImageView *imgDown = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 25, 25)];
        imgDown.image = [UIImage imageNamed:@"searchFilterArrow1"];
        imgDown.image = [imgDown.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
        [imgDown setTintColor:[UIColor orangeColor]];

        textAttachment.image = imgDown.image;

        NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];

        [readMoreAttributed replaceCharactersInRange:NSMakeRange(12, 1) withAttributedString:attrStringWithImage];
    
    else
        [readMoreAttributed addAttribute:NSUnderlineStyleAttributeName
                                   value:@(NSUnderlineStyleSingle)
                                   range:NSMakeRange(1, 9)];
        NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
        UIImageView *imgup = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 25, 25)];
        imgup.image = [UIImage imageNamed:@"searchFilterArrow2"];
        imgup.image = [imgup.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
        [imgup setTintColor:[UIColor orangeColor]];

        textAttachment.image = imgup.image;

        NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];

        [readMoreAttributed replaceCharactersInRange:NSMakeRange(11, 1) withAttributedString:attrStringWithImage];
    

    [answerAttributed appendAttributedString:readMoreAttributed];
    label.attributedText = answerAttributed;

    UITapGestureRecognizer *readMoreGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(readMoreDidClickedGesture:)];
    readMoreGesture.numberOfTapsRequired = 1;
    [label addGestureRecognizer:readMoreGesture];

    label.userInteractionEnabled = YES;

else 

    NSLog(@"No need for 'Read More'...");



【讨论】:

【参考方案11】:
func updateData(_ label: UILabel) 
    self.headerLabel.text = detailViewModel.firstTitle
    self.detailLabel.text = detailViewModel.firstContent

    headerTitle = detailViewModel.firstTitle
    detailTitle = detailViewModel.firstContent

    DispatchQueue.main.async 
        let readMoreText = "...View More"
        let stringColor: UIColor = UIColor.blue
        let attributes = [NSForegroundColorAttributeName: stringColor]

        let numberOfLines = self.detailLabel.numberOfVisibleLines

        if numberOfLines > 2 

            let lengthForVisibleString: Int = self.fit( self.detailLabel.text, into: self.detailLabel)
            let mutableString = self.detailLabel.text ?? ""
            let trimmedString = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: (self.detailLabel?.text?.count ?? 0) - lengthForVisibleString), with: "")
            let readMoreLength: Int = readMoreText.count
            let trimmedForReadMore = (trimmedString as NSString).replacingCharacters(in: NSRange(location: trimmedString.count - readMoreLength, length: readMoreLength), with: "")
            let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSFontAttributeName: self.detailLabel.font])

            let readMoreAttributed = NSMutableAttributedString(string: readMoreText, attributes: attributes)
            answerAttributed.append(readMoreAttributed)
            self.detailLabel.attributedText = answerAttributed


            let readMoreGesture = UITapGestureRecognizer(target: self, action:#selector(FundDetailsTableViewCell.showViewMore(_:)))
            readMoreGesture.numberOfTapsRequired = 1
            self.detailLabel.addGestureRecognizer(readMoreGesture)
            self.detailLabel.isUserInteractionEnabled = true
        
    


func fit(_ string: String?, into label: UILabel?) -> Int 
    guard let stringObjc = string as NSString? else 
        return 0
    
    let font: UIFont = label?.font ?? UIFont.systemFont(ofSize: 14.0)
    let mode: NSLineBreakMode? = label?.lineBreakMode
    let labelWidth: CGFloat? = label?.frame.size.width
    let labelHeight: CGFloat? = label?.frame.size.height
    let sizeConstraint = CGSize(width: labelWidth ?? 0.0, height: CGFloat.greatestFiniteMagnitude)
    let attributes = [NSFontAttributeName: font]

    let device = UIDevice.current
    let iosVersion = Double(device.systemVersion) ?? 0

    if iosVersion > 7 
        let attributedText = NSAttributedString(string: string ?? "", attributes: attributes)
        let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)
        do 
            if boundingRect.size.height > (labelHeight ?? 0.0) 
                var index: Int = 0
                var prev: Int
                let characterSet = CharacterSet.whitespacesAndNewlines
                repeat 
                    prev = index
                    if mode == .byCharWrapping 
                        index += 1
                     else 
                        index = Int((string as NSString?)?.rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: (string?.count ?? 0) - index - 1)).location ?? 0)
                    
                 while index != NSNotFound && index < (string?.count ?? 0)
                    && (stringObjc.substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).size.height) <= labelHeight!
                return prev;
            
        
     else 
        if stringObjc.size(attributes: attributes).height > labelHeight! 
            var index: Int = 0
            var prev: Int
            let characterSet = CharacterSet.whitespacesAndNewlines
            repeat 
                prev = index
                if mode == .byCharWrapping 
                    index += 1
                 else 
                    index = stringObjc.rangeOfCharacter(from: characterSet, options: NSString.CompareOptions.caseInsensitive, range: NSRange(location: index + 1, length: stringObjc.length - index - 1)).location
                

             while index != NSNotFound && index < (string?.count)! && (stringObjc.substring(to: index) as NSString).size(attributes: attributes).height <= labelHeight!
            return prev

        
    
    return (string?.count)!


func showViewMore(_ sender: UITapGestureRecognizer) 



extension UILabel 
    var numberOfVisibleLines: Int 
        let textSize = CGSize(width: CGFloat(self.frame.size.width), height: CGFloat(MAXFLOAT))
        let rHeight: Int = lroundf(Float(self.sizeThatFits(textSize).height))
        let charSize: Int = lroundf(Float(self.font.pointSize))
        return rHeight / charSize
    

【讨论】:

【参考方案12】:

为了行动

 let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapFunction))
 Urlabel.isUserInteractionEnabled = true
 Urlabel.addGestureRecognizer(tap)

 @objc
    func tapFunction(sender:UITapGestureRecognizer) 


    

【讨论】:

【参考方案13】:

对于标签上的操作,如果使用 CollectionView 或 TableView,您可以使用委托方法来执行操作。

func showMore(cell: CustomCell) 
    guard let indexPath = self.tableView.indexPath(for: cell) else 
        return
    
    let cell = tableView.cellForRow(at: indexPath) as! CustomCell
    tableView.beginUpdates()
    cell.label.text = "your complete text"
    tableView.endUpdates()

这会更新标签并根据需要显示全文 使用 Lance Samaria 答案并为单元格添加操作。

【讨论】:

【参考方案14】:

这是另一个使用 Swift 5 的解决方案。

参考

UITextView: Find location of ellipsis in truncated text https://***.com/users/1847511/naloiko-eugene解决方案

结果

步骤

逻辑很简单。

    检查标签的文本是否被截断。 使用 LayoutManager 获取标签文本中省略号起点的索引 使用省略号索引对文本进行切片 用... more替换子串(你可以改) 在标签上添加UITapGesture 从手势位置获取标签的属性字符串中的索引 -> private func getIndex(from point: CGPoint) -&gt; Int? 检查手势位置 -> func didTapInRange(_ point: CGPoint, targetRange: NSRange) -&gt; Bool

示例代码

let loremIpsumString = """
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
    """

    private var expandableTextRange: NSRange?
//...in ViewDidLoad
label.text = loremIpsumString

if label.isTruncatedText 
            expandableTextRange = label.setExpandActionIfPossible("More", textColor: .brown)
        

//Add IBAction on the label
@IBAction func didTapLabel(_ sender: UITapGestureRecognizer) 
        guard let expandRange = expandableTextRange else 
            return
        
        let tapLocation = sender.location(in: label)
        if label.didTapInRange(tapLocation, targetRange: expandRange) 
            label.numberOfLines = 0
            label.text = loremIpsumString
        
        else 
            resultLabel.text = "You tapped the area outside More."
        
    



extension UILabel 
    var isTruncatedText: Bool 
        guard let height = textHeight else 
            return false
        
        return height > bounds.size.height
    
    
    var textHeight: CGFloat? 
        guard let labelText = text else 
            return nil
        
        let attributes: [NSAttributedString.Key: UIFont] = [.font: font]
        let labelTextSize = (labelText as NSString).boundingRect(
            with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
            options: .usesLineFragmentOrigin,
            attributes: attributes,
            context: nil
        ).size
        return ceil(labelTextSize.height)
    
    
    @discardableResult
    func setExpandActionIfPossible(_ text: String, textColor: UIColor? = nil) -> NSRange? 
        guard isTruncatedText, let visibleString = visibleText else 
            return nil
        
        let defaultTruncatedString = "... "
        let fontAttribute: [NSAttributedString.Key: UIFont] = [.font: font]
        let expandAttributedString: NSMutableAttributedString = NSMutableAttributedString(
            string: defaultTruncatedString,
            attributes: fontAttribute
        )
        let customExpandAttributes: [NSAttributedString.Key: Any] = [
            .font: font as Any,
            .foregroundColor: (textColor ?? self.textColor) as Any
        ]
        let customExpandAttributedString = NSAttributedString(string: "\(text)", attributes: customExpandAttributes)
        expandAttributedString.append(customExpandAttributedString)
        
        let visibleAttributedString = NSMutableAttributedString(string: visibleString, attributes: fontAttribute)
        guard visibleAttributedString.length > expandAttributedString.length else 
            return nil
        
        let changeRange = NSRange(location: visibleAttributedString.length - expandAttributedString.length, length: expandAttributedString.length)
        visibleAttributedString.replaceCharacters(in: changeRange, with: expandAttributedString)
        attributedText = visibleAttributedString
        return changeRange
    
    
    var visibleText: String? 
        guard isTruncatedText,
            let labelText = text,
            let lastIndex = truncationIndex else 
            return nil
        
        let visibleTextRange = NSRange(location: 0, length: lastIndex)
        guard let range = Range(visibleTextRange, in: labelText) else 
            return nil
        
        return String(labelText[range])
    
    
    //https://***.com/questions/41628215/uitextview-find-location-of-ellipsis-in-truncated-text/63797174#63797174
    var truncationIndex: Int? 
        guard let text = text, isTruncatedText else 
            return nil
        
        let attributes: [NSAttributedString.Key: UIFont] = [.font: font]
        let attributedString = NSAttributedString(string: text, attributes: attributes)
        let textContainer = NSTextContainer(
            size: CGSize(width: frame.size.width,
                         height: CGFloat.greatestFiniteMagnitude)
        )
        textContainer.maximumNumberOfLines = numberOfLines
        textContainer.lineBreakMode = lineBreakMode

        let layoutManager = NSLayoutManager()
        layoutManager.addTextContainer(textContainer)

        let textStorage = NSTextStorage(attributedString: attributedString)
        textStorage.addLayoutManager(layoutManager)

        //Determine the range of all Glpyhs within the string
        var glyphRange = NSRange()
        layoutManager.glyphRange(
            forCharacterRange: NSMakeRange(0, attributedString.length),
            actualCharacterRange: &glyphRange
        )

        var truncationIndex = NSNotFound
        //Iterate over each 'line fragment' (each line as it's presented, according to your `textContainer.lineBreakMode`)
        var i = 0
        layoutManager.enumerateLineFragments(
            forGlyphRange: glyphRange
        )  rect, usedRect, textContainer, glyphRange, stop in
            if (i == self.numberOfLines - 1) 
                //We're now looking at the last visible line (the one at which text will be truncated)
                let lineFragmentTruncatedGlyphIndex = glyphRange.location
                if lineFragmentTruncatedGlyphIndex != NSNotFound 
                    truncationIndex = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: lineFragmentTruncatedGlyphIndex).location
                
                stop.pointee = true
            
            i += 1
        
        return truncationIndex
    
    
    //https://***.com/questions/1256887/create-tap-able-links-in-the-nsattributedstring-of-a-uilabel
    private func getIndex(from point: CGPoint) -> Int? 
        guard let attributedString = attributedText, attributedString.length > 0 else 
            return nil
        
        let textStorage = NSTextStorage(attributedString: attributedString)
        let layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        let textContainer = NSTextContainer(size: frame.size)
        textContainer.lineFragmentPadding = 0
        textContainer.maximumNumberOfLines = numberOfLines
        textContainer.lineBreakMode = lineBreakMode
        layoutManager.addTextContainer(textContainer)

        let index = layoutManager.characterIndex(
            for: point,
            in: textContainer,
            fractionOfDistanceBetweenInsertionPoints: nil
        )
        return index
    
    
    func didTapInRange(_ point: CGPoint, targetRange: NSRange) -> Bool 
        guard let indexOfPoint = getIndex(from: point) else 
            return false
        
        return indexOfPoint > targetRange.location &&
            indexOfPoint < targetRange.location + targetRange.length
    



【讨论】:

怎么用,没看懂【参考方案15】:

TTTAttributedLabel:- 使用下面的代码行设置字体

attributedLessText = NSMutableAttributedString(string: subStringWithReadMore, 属性: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17)])

【讨论】:

欢迎来到 Stack Overflow。在 Stack Overflow 上不鼓励仅使用代码的答案,因为它们没有解释它是如何解决问题的。请编辑您的答案,以解释它如何回答问题以及它如何改进现有答案,以便对其他有类似问题的用户有用。【参考方案16】:

我无法在这里工作,所以我发布了我的解决方案。我有一个 UILabel,它应该是 3 行,在文本的末尾应该是……阅读更多。我做了一个 UIFont 扩展来计算具有特定字体和宽度的字符串的高度,然后我做了一个 String 扩展,它递归地使字符串变小,直到它适合 x 行。

extension String 
    /**
This method returns a substring of a string that fits in a label with a specific width and number of lines
It has optional suffix so you can add your own ellipsis, like "... Read more"
 - Parameter width: The label width that constrains the text. Make sure to call it after subViews have been laid out
 - Parameter lines: Number of allowed lines in the label
 - Parameter font: The font to use with the label
 - Parameter suffix: Custom string that will be added to the string and will fit within the width/lines constraint
 - Returns: A substring that fits within the constraints given
 */
func textThatFits(width: CGFloat, lines: Int, font: UIFont, suffix: String = "") -> String 
    let lineHeight = font.lineHeight
    let completeString = self + suffix
    let size = font.sizeOfString(completeString, constrainedToWidth: width)
    if size.height > lineHeight * CGFloat(lines) 
        let partialString = self.components(separatedBy: " ").dropLast().joined(separator: " ")
        return partialString.textThatFits(width: width, lines: lines, font: font, suffix: suffix)
     else 
        return completeString
    



extension UIFont 
    /**
 Calculate the height of a string with this font and constrained width
 - Parameter string: String to calculate size for
 - Parameter width: The constrained width for the bounding box when calculating the size
 - Returns: Size of string contained in bounding rect of width and max height
 */
func sizeOfString(_ string: String, constrainedToWidth width: Double) -> CGSize 
    return (string as NSString).boundingRect(
        with: CGSize(width: width, height: .greatestFiniteMagnitude),
        options: [.usesFontLeading, .usesLineFragmentOrigin],
        attributes: [.font: self],
        context: nil).size


然后我不得不从 viewDidLayoutSubviews 调用它,因为在那之前我的标签宽度不正确。最后,我是这样使用的:

private func setReadMoreText() 
    let readMoreSuffix = "... Read more"
    let font = // YOUR_FONT
    let fittingString = YOUR_LONG_STRING.textThatFits(width: YOUR_UILABEL.frame.width, lines: NUMBER_OF_LINES, font: font, suffix: readMoreSuffix)
    // This is not needed but I wanted the Read more text to be colored. You could just set your fittingString directly as text.
    let privacyAttrString = NSMutableAttributedString(string: fittingString, attributes: [.font: font])
    privacyAttrString.addAttributes([.foregroundColor: YOUR_COLOR], range: NSRange(location: fittingString.count - readMoreSuffix.count + 4, length: readMoreSuffix.count - 4))
    self.YOUR_UILABEL.text = privacyAttrString

【讨论】:

【参考方案17】:

你知道UILabel没有触摸动作吗?所以如果 UILabel 中的整个文本,你不能触摸“...阅读更多”。

注意:我的解决方案是,在UILabel末尾添加一个清晰的背景按钮。

【讨论】:

以上是关于在 UILabel 末尾添加“...阅读更多”的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中的文本末尾实现“阅读更多”样式按钮

在ios中截断后如何在UILabel文本的末尾添加“更多”

TTTAttributedLabel 为截断添加操作(“阅读更多”文本)

如何使用 TTTAttributedLabel 添加“阅读更多”

使用swift在UiStackview中设置UiLabel的高度

UILabel 在添加文本时反弹