Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I locate the CGRect for a substring of text in a UILabel?

For a given NSRange, I'd like to find a CGRect in a UILabel that corresponds to the glyphs of that NSRange. For example, I'd like to find the CGRect that contains the word "dog" in the sentence "The quick brown fox jumps over the lazy dog."

Visual description of problem

The trick is, the UILabel has multiple lines, and the text is really attributedText, so it's a bit tough to find the exact position of the string.

The method that I'd like to write on my UILabel subclass would look something like this:

 - (CGRect)rectForSubstringWithRange:(NSRange)range; 

Details, for those who are interested:

My goal with this is to be able to create a new UILabel with the exact appearance and position of the UILabel, that I can then animate. I've got the rest figured out, but it's this step in particular that's holding me back at the moment.

What I've done to try and solve the issue so far:

  • I'd hoped that with iOS 7, there'd be a bit of Text Kit that would solve this problem, but most every example I've seen with Text Kit focuses on UITextView and UITextField, rather than UILabel.
  • I've seen another question on Stack Overflow here that promises to solve the problem, but the accepted answer is over two years old, and the code doesn't perform well with attributed text.

I'd bet that the right answer to this involves one of the following:

  • Using a standard Text Kit method to solve this problem in a single line of code. I'd bet it would involve NSLayoutManager and textContainerForGlyphAtIndex:effectiveRange
  • Writing a complex method that breaks the UILabel into lines, and finds the rect of a glyph within a line, likely using Core Text methods. My current best bet is to take apart @mattt's excellent TTTAttributedLabel, which has a method that finds a glyph at a point - if I invert that, and find the point for a glyph, that might work.

Update: Here's a github gist with the three things I've tried so far to solve this issue: https://gist.github.com/bryanjclark/7036101

like image 469
bryanjclark Avatar asked Oct 17 '13 03:10

bryanjclark


People also ask

How can I create a UILabel with strikethrough text?

Swift5 UILabel Extension. Use this code in that case. Show activity on this post. Change the text property to attributed and select the text and right click to get the font property. Click on the strikethrough.


2 Answers

Following Joshua's answer in code, I came up with the following which seems to work well:

- (CGRect)boundingRectForCharacterRange:(NSRange)range {     NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];     NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];     [textStorage addLayoutManager:layoutManager];     NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];     textContainer.lineFragmentPadding = 0;     [layoutManager addTextContainer:textContainer];      NSRange glyphRange;      // Convert the range for glyphs.     [layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];      return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]; } 
like image 153
Luke Rogers Avatar answered Sep 25 '22 06:09

Luke Rogers


Building off of Luke Rogers's answer but written in swift:

Swift 2

extension UILabel {     func boundingRectForCharacterRange(_ range: NSRange) -> CGRect? {          guard let attributedText = attributedText else { return nil }          let textStorage = NSTextStorage(attributedString: attributedText)         let layoutManager = NSLayoutManager()          textStorage.addLayoutManager(layoutManager)          let textContainer = NSTextContainer(size: bounds.size)         textContainer.lineFragmentPadding = 0.0          layoutManager.addTextContainer(textContainer)          var glyphRange = NSRange()          // Convert the range for glyphs.         layoutManager.characterRangeForGlyphRange(range, actualGlyphRange: &glyphRange)          return layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textContainer)             } } 

Example Usage (Swift 2)

let label = UILabel() let text = "aa bb cc" label.attributedText = NSAttributedString(string: text) let sublayer = CALayer() sublayer.borderWidth = 1 sublayer.frame = label.boundingRectForCharacterRange(NSRange(text.range(of: "bb")!, in: text)) label.layer.addSublayer(sublayer) 

Swift 3/4

extension UILabel {     func boundingRect(forCharacterRange range: NSRange) -> CGRect? {          guard let attributedText = attributedText else { return nil }          let textStorage = NSTextStorage(attributedString: attributedText)         let layoutManager = NSLayoutManager()          textStorage.addLayoutManager(layoutManager)          let textContainer = NSTextContainer(size: bounds.size)         textContainer.lineFragmentPadding = 0.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)            } } 

Example Usage (Swift 3/4)

let label = UILabel() let text = "aa bb cc" label.attributedText = NSAttributedString(string: text) let sublayer = CALayer() sublayer.borderWidth = 1 sublayer.frame = label.boundingRect(forCharacterRange: NSRange(text.range(of: "bb")!, in: text)) label.layer.addSublayer(sublayer) 
like image 30
NoodleOfDeath Avatar answered Sep 25 '22 06:09

NoodleOfDeath