3

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

calvin sugianto
  • 610
  • 7
  • 27
  • Is it possible to let us know your purpose of searching character co-ordinate? – elk_cloner May 26 '17 at 11:32
  • Why `var glyphRange: NSRange`. In Objective-C, it was using `&glyphRange`, so the method was `layoutManager.characterRange()` was actually giving it a value. That's why. It's the same "logic" used for `[myUIColor getRed:&red green:&green blue:&blue alpha:&alpha];`. – Larme May 26 '17 at 11:39
  • @elk_cloner i need to draw chords above my specific character, for example: "I'm in love with the [G]shape of you" I must draw `G` exactly at the top of s word from "shape" @Larme yeah, i know the method is `layoutManager.characterRange()` , but i'm confused how to fill actualGlyphRange params? – calvin sugianto May 26 '17 at 13:01
  • Possible duplicate of [How do I locate the CGRect for a substring of text in a UILabel?](https://stackoverflow.com/questions/19417776/how-do-i-locate-the-cgrect-for-a-substring-of-text-in-a-uilabel) – Cœur Mar 23 '18 at 10:46

1 Answers1

0

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.

thm
  • 1,217
  • 10
  • 12