Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITextView: Find location of ellipsis in truncated text

I have a UITextView with some attributed text, where the textContainer.maximumNumberOfLine is set (3 in this case).

I'd like to find the index of the ellipsis character within the character range of the attributed string.

E.g:

Original String:

"Lorem ipsum dolor sit amet, consectetur adipiscing elit"

String as displayed, after truncation:

Lorem ipsum dolor sit amet, consectetur...

How do I determine the index of the ...?

like image 356
Tim Malseed Avatar asked Jan 13 '17 05:01

Tim Malseed


1 Answers

Here's an extension function to NSAttributedString which does the job. Works for single & multiline text.

This took me all of about 8 hours to figure out, so I thought I'd post it as a Q&A.

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

Note that this has not been tested beyond some simple cases. There may be edge cases requiring some adjustments..

like image 197
Tim Malseed Avatar answered Oct 31 '22 16:10

Tim Malseed