Create UITextRange from NSRange
You can create a text range with the method textRangeFromPosition:toPosition
. This method requires two positions, so you need to compute the positions for the start and the end of your range. That is done with the method positionFromPosition:offset
, which returns a position from another position and a character offset.
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView{ UITextPosition *beginning = textView.beginningOfDocument; UITextPosition *start = [textView positionFromPosition:beginning offset:range.location]; UITextPosition *end = [textView positionFromPosition:start offset:range.length]; UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end]; CGRect rect = [textView firstRectForRange:textRange]; return [textView convertRect:rect fromView:textView.textInputView];}
It is a bit ridiculous that seems to be so complicated. A simple "workaround" would be to select the range (accepts NSRange) and then read the selectedTextRange (returns UITextRange):
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView { textView.selectedRange = range; UITextRange *textRange = [textView selectedTextRange]; CGRect rect = [textView firstRectForRange:textRange]; return rect;}
This worked for me even if the textView is not first responder.
If you don't want the selection to persist, you can either reset the selectedRange:
textView.selectedRange = NSMakeRange(0, 0);
...or save the current selection and restore it afterwards
NSRange oldRange = textView.selectedRange;// do something// then check if the range is still valid andtextView.selectedRange = oldRange;
Swift 4 of Andrew Schreiber's answer for easy copy/paste
extension NSRange { func toTextRange(textInput:UITextInput) -> UITextRange? { if let rangeStart = textInput.position(from: textInput.beginningOfDocument, offset: location), let rangeEnd = textInput.position(from: rangeStart, offset: length) { return textInput.textRange(from: rangeStart, to: rangeEnd) } return nil }}