9

I'm trying to display an UIPopoverController from the rect of a selected text in an UITextView, How can I get the selected text CGRect ?

Thanks!

Till
  • 27,559
  • 13
  • 88
  • 122
  • If this question is what I think what it is, UIPopoverController in the title is probably misleading? You want to get the CGPoint from the text selection, it is nothing special to UIPopoverController, right? – barley Dec 30 '11 at 21:03
  • Fixed the title as your suggestion is valid @barley. – Till Dec 30 '11 at 21:28

3 Answers3

36

I think [UITextInput selectedTextRange] and [UITextInput caretRectForPosition:] is what you are looking for.

[UITextInput selectedTextRange] returns the selected range in character

[UITextInput caretRectForPosition:] returns the CGRect of the character range in this input.

UITextView conforms to UITextInput (since iOS 5), so you can use these methods for your UITextView instance.

It is going to be something like this.

UITextRange * selectionRange = [textView selectedTextRange];
CGRect selectionStartRect = [textView caretRectForPosition:selectionRange.start];
CGRect selectionEndRect = [textView caretRectForPosition:selectionRange.end];
CGPoint selectionCenterPoint = (CGPoint){(selectionStartRect.origin.x + selectionEndRect.origin.x)/2,(selectionStartRect.origin.y + selectionStartRect.size.height / 2)};

EDIT : Since the sample code became a little hard to get, I added an image for complementing.

An image that illustrates what local variables represent

omz
  • 53,243
  • 5
  • 129
  • 141
barley
  • 4,403
  • 25
  • 28
  • Small issue, you call it center point but you are actually calculating the bottom right point. – Till Dec 30 '11 at 21:14
  • @Till yeah, originally I thought [UITextInput caretRectForPosition:] will take UITextRange as input, but (as the name suggests) it actually takes UITextPosition. I fixed the sample code, and I think it is now more accurate although a little harder to get. And thanks for the edition! – barley Dec 30 '11 at 21:24
  • You still have a small issue in your center calculation :D ... check the X-coordinate again, the brackets are incorrectly set to calc the center. – Till Dec 30 '11 at 21:27
  • Well, I think it was more or less accurate. I sample coded and it worked as I expected. I AM ignoring the width of selectionXXXRect since it is very small, but other than that, the sample code should calculate approximate center quite okay. I added an image for easier understanding. Please let me know if I am still doing something wrong, or if there is something I can improve... – barley Dec 30 '11 at 22:07
  • 1
    Ow, gee - see, your additions made it obvious to me that I was wrong. I think your answer is really well done and should deserve a little extra... Will add a bounty once that is possible. – Till Dec 30 '11 at 22:14
23

There are some situations where barley's answer won't actually give the center of the selection. For example:

Screenshot showing an example of where using the caret rects would not be accurate

In this case you can see the Copy/Paste menu is displaying in the center of the selection, which spans the entire width of the text field. But calculating the center of the two caret rects would give a position much further to the right.

You can get a more precise result using selectionRectsForRange:

UITextRange *selectionRange = [textView selectedTextRange];
NSArray *selectionRects = [self.textView selectionRectsForRange:selectionRange];
CGRect completeRect = CGRectNull;
for (UITextSelectionRect *selectionRect in selectionRects) {
    if (CGRectIsNull(completeRect)) {
        completeRect = selectionRect.rect;
    } else completeRect = CGRectUnion(completeRect,selectionRect.rect);
}

It's also worth clarifying that if you're still supporting iOS 4 and using either of these answers, you'll need to make sure these methods are supported before calling them: if ([textView respondsToSelector:@selector(selectedTextRange)]) { …

robotspacer
  • 2,732
  • 2
  • 28
  • 50
  • 3
    You don't actually need the check for CGRectIsNull as CGRectUnion already handles this. According to the documentation for CGRectUnion: "If either of the rectangles is a null rectangle, a copy of the other rectangle is returned" –  Jan 13 '14 at 18:39
1

Swift 5 version:

if let selectionRect = textView.selectedTextRange {
        let selectionRects = textView.selectionRects(for: selectionRect)
        var completeRect = CGRect.null
        for rect in selectionRects {
            if (completeRect.isNull) {
                completeRect = rect.rect
            } else {
                completeRect = rect.rect.union(completeRect)
            }
        }
    }
mkhoshpour
  • 845
  • 1
  • 10
  • 22