【问题标题】:UITextView: Find location of ellipsis in truncated textUITextView:在截断文本中查找省略号的位置
【发布时间】:2017-05-28 10:45:04
【问题描述】:

我有一个带有一些属性文本的UITextView,其中设置了textContainer.maximumNumberOfLine(在本例中为3)。

我想在属性字符串的字符范围内找到省略号字符的索引。

例如:

原始字符串:

"Lorem ipsum dolor sit amet, consectetur adipiscing elit"

截断后显示的字符串:

Lorem ipsum dolor sit amet, consectetur...

如何确定...的索引?

【问题讨论】:

    标签: ios uitextview nsattributedstring truncation


    【解决方案1】:

    这是 NSAttributedString 的扩展函数,它可以完成这项工作。适用于单行和多行文本。

    我花了大约 8 个小时才弄清楚,所以我想我会把它作为问答发布。

    (斯威夫特 2.2)

    /**
        Returns the index of the ellipsis, if this attributed string is truncated, or NSNotFound otherwise.
    */
    func truncationIndex(maximumNumberOfLines: Int, width: CGFloat) -> Int {
    
        //Create a dummy text container, used for measuring & laying out the text..
    
        let textContainer = NSTextContainer(size: CGSize(width: width, height: CGFloat.max))
        textContainer.maximumNumberOfLines = maximumNumberOfLines
        textContainer.lineBreakMode = NSLineBreakMode.ByTruncatingTail
    
        let layoutManager = NSLayoutManager()
        layoutManager.addTextContainer(textContainer)
    
        let textStorage = NSTextStorage(attributedString: self)
        textStorage.addLayoutManager(layoutManager)
    
        //Determine the range of all Glpyhs within the string
    
        var glyphRange = NSRange()
        layoutManager.glyphRangeForCharacterRange(NSMakeRange(0, self.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.enumerateLineFragmentsForGlyphRange(glyphRange) { (rect, usedRect, textContainer, glyphRange, stop) in
            if (i == maximumNumberOfLines - 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.truncatedGlyphRangeInLineFragmentForGlyphAtIndex(lineFragmentTruncatedGlyphIndex).location
                }
                stop.memory = true
            }
            i += 1
        }
    
        return truncationIndex
    }
    

    请注意,除了一些简单的情况外,这还没有经过测试。可能存在需要一些调整的边缘情况..

    【讨论】:

    • 查看 NSLayoutManager 的文档,似乎使用 firstUnlaidCharacter()firstUnlaidGlyph() 而不是枚举行片段是更好的选择?
    • @DrOverbuild 我真的不确定,自从我从事这个工作已经有几年了。我不记得当时是否可行,我是否尝试过但没有奏效,或者这只是我没有考虑的一个选项。
    • @TimMalseed 好的,所以在处理了半天之后,我了解到firstUnlaidCharacter()firstUnlaidGlyph() 的工作方式与遍历每个行片段一样有效,但无论如何都没有运行。我还必须将换行模式更改为.byWordWrap。但除此之外,这真的很有帮助。
    【解决方案2】:

    我用 Swift 5 更新了@Tim Malseed 的解决方案

    extension UILabel {
    
     var truncationIndex: Int? {
            guard let text = text 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 = NSLineBreakMode.byTruncatingTail
    
            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
        }
    }
    

    【讨论】:

    • 我认为当您在 UILabel 中将行数属性指定为“0”时这不起作用,因为建议在标签中布局尽可能多的行。我错了吗 ? ?
    • 最初的问题是针对 UITextView 而您正在对 UILabel 进行操作。
    猜你喜欢
    • 2011-09-17
    • 2016-10-18
    • 2013-07-17
    • 2011-05-17
    • 1970-01-01
    • 2014-08-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多