9
votes

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!

3
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
Fixed the title as your suggestion is valid @barley.Till

3 Answers

36
votes

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

21
votes

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)]) { …

1
votes

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