UITextView:查找截断文本中省略号的位置

5

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

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

例如:

原始字符串:

"Lorem ipsum dolor sit amet, consectetur adipiscing elit"

截断后显示的字符串:

Lorem ipsum dolor sit amet, consectetur...

如何确定...的索引?

2个回答

10

这是一个NSAttributedString的扩展函数,可以处理单行和多行文本。

我花了大约8个小时才想到这个方法,所以我觉得我可以将它作为问答帖子发布出来。

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

请注意,这个还没有经过除了一些简单的情况以外的测试。可能会有需要进行一些调整的边缘情况。


1
看了一下NSLayoutManager的文档,似乎使用firstUnlaidCharacter()或者firstUnlaidGlyph()比枚举行片段更好? - DrOverbuild
1
@DrOverbuild,我真的不确定,因为已经有几年没接触这个了。我不记得当时是否可能,我是否尝试过但失败了,或者那只是我没有考虑的选项。 - Tim Malseed
3
好的,经过半天的工作,我发现firstUnlaidCharacter()firstUnlaidGlyph()这两个方法与迭代每个行片段一样有效,而且后者根本就没有运行。此外,我还必须将换行模式更改为“.byWordWrap”。 除此之外,这真的很有帮助。 - DrOverbuild

0

我使用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”时,它可能无法正常工作,因为建议在标签中布局尽可能多的行。我错了吗? - XLE_22
原始问题是关于UITextView的,而你正在对UILabel进行操作。 - Claus Jørgensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接