Locate character coordinate in Custom UILabel with attributedText

1k Views Asked by At

i need to locate my character location in my UILabel (it has ParagraphLineSpacing and AttributedText with multiline),

i have got my character's index, but now i can't get X and Y coordinate from my index.

i Have found this http://techqa.info/programming/question/19417776/how-do-i-locate-the-cgrect-for-a-substring-of-text-in-a-uilabel

and i translated to my Swift 3.1 code

func boundingRect(forCharacterRange range: NSRange) -> CGRect {

    let textStorage = NSTextStorage(attributedString: self.attributedText!)
    let layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)
    let textContainer = NSTextContainer(size: bounds.size)
    textContainer.lineFragmentPadding = 0
    layoutManager.addTextContainer(textContainer)
    var glyphRange: NSRange
    // Convert the range for glyphs.

    layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: glyphRange)
    return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)

}

but, unfortunately, i can't really use this code because actualGlyphRange ask NSRangePointer, not NSRange, so i changed my translated code to

func boundingRect(forCharacterRange range: NSRange) -> CGRect {

    let textStorage = NSTextStorage(attributedString: self.attributedText!)
    let layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)
    let textContainer = NSTextContainer(size: bounds.size)
    textContainer.lineFragmentPadding = 0
    layoutManager.addTextContainer(textContainer)
    //var glyphRange: NSRange

    let a = MemoryLayout<NSRange>.size
    let pointer:NSRangePointer = NSRangePointer.allocate(capacity: a)
    layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: pointer)
    return layoutManager.boundingRect(forGlyphRange: range, in: textContainer)
}

i don't understand what

 var glyphRange: NSrange 

usage, so i removed it and now the code is working, but the result is 60% not accurate especially when my character located on the second line or the third line. Do i messed up the translation here? Or are there any better method to get my character coordinate accurately?

i use

NSMakeRange(index, 1) 

for my params to locate one specific character

=======UPDATED=======

I have tried custom UITextView to access its layout Manager, but unfortunately, the position is still inaccurate if there are 2 lines or more. (only accurate if there is only 1 line in my textView)

class LyricTextView: UITextView {
  func boundingRect(forCharacterRange range: NSRange) -> CGRect {
    let inset = self.textContainerInset
    let rect = self.layoutManager.boundingRect(forGlyphRange: range, in: textContainer).offsetBy(dx: inset.left, dy: inset.top)

    return rect
  }
}

Am i missing something in this new code? It is getting nearly done

1

There are 1 best solutions below

2
On

An easier fix for your compilation error would be to use this:

var glyphRange = NSRange()
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)

However, when I tried it, I also could only get correct rectangles for text on the first line.

If using a UITextView is ok for you, you have access to its layout manager and text container:

@IBOutlet var textView: UITextView!
...

let rect = textView!.layoutManager.boundingRect(
    forGlyphRange: glyphRange, in: textView!.textContainer)

It seems you also need to take into account the text view's text container inset so the following worked for me to get a bounding rect for text on the second line:

let inset = textView!.textContainerInset
let rect = textView!.layoutManager.boundingRect(
    forGlyphRange: glyphRange, in: textView!.textContainer)
    .offsetBy(dx: inset.left, dy: inset.top)

I'd be interested if somebody finds a solution that works for UILabel.