1

I want my UITextView to scroll to a specific point (a word, or better a breakpoint: ">>>") when user presses a button outside the textview itself (on iOS). The font size can vary depending on user interaction (i.e. the user can rise or lower font size). The text itself is pasted by the user (who interactively adds the breakpoints).

I'm trying to achieve this by getting the y positions (in pixels) of the breakpoints (">>>") and then setting the offset of the textView in accordance (with setContentOffset).

The problem is that the y points I get are wrong!

Here it is a snippet of my code (for just one the ">>>" recurrences):

NSRange range = [self.textview.text rangeOfString:@">>>"];

UITextPosition *beginning = self.textview.beginningOfDocument;
UITextPosition *start = [self.textview positionFromPosition:beginning offset:range.location];
UITextPosition *end = [self.textview positionFromPosition:start offset:range.length];
UITextRange *textRange = [self.textview textRangeFromPosition:start toPosition:end];
CGRect rect = [self.textview firstRectForRange:textRange];

[self.textview setContentOffset:CGPointMake(0, rect.origin.y)];

What's wrong with my approach? I feel there's a better (and easier) way to do the same thing...

PS: I found another topic related to the same problem but it's in swift and I can't translate it: Scroll UITextView to specific text

Mafradan
  • 23
  • 1
  • 6

2 Answers2

1

You need to convert the rect to the text views coordinate space.

Try it like this:

// get the range
NSRange range = [self.textview.text rangeOfString:@">>>"];

// get the rect in the textview's coordinate space
CGRect foundRect = [self frameOfTextRange:range inTextView:self.textview];

// scroll the found rect into view
[self.textview setContentOffset:CGPointMake(0.0, foundRect.origin.y) animated:YES];

using this method (found here: https://stackoverflow.com/a/9606413/6257435):

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView
{
    // ensure textview layouManager has been processed
    [textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];

    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];
}

You will, of course, need to add error handling (for text not found, etc).

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Ok, it works for the first ">>>" if it's in the first lines of text. If I have a long text and, say, I set just one ">>>" at the 40th line of it, it gets to the point only at the third or fourth try (I mean, pressing 3 or 4 times the button that triggers the action 'frameOfTextRange'). And each time I press the button, by NSLog I get different values of foundRect.origin.y (7567.000000 / 10591.000000 / 12775.000000). The right one is the last one... – Mafradan Aug 29 '18 at 19:47
  • ok... it works perfectly if I resize the textview (programmatically) at 20.000 pixels in height (i.e. more than the length of the text), but I'm afraid it's "illegal" (will I have memory problems with it?). – Mafradan Aug 29 '18 at 20:22
  • ah... we have to make sure the textView's layoutManager has processed the content. See my edited answer (first line in`frameOfTextRange` method). – DonMag Aug 29 '18 at 20:33
1

I tried above thing in swift and realise there is no point in calculating rect and then scrolling to that rect. You can simply call func scrollRangeToVisible(_ range: NSRange) to scroll to the word in textView.

let range: Range = self.textView.text.range(of: "amendment")!
let nsRange = NSRange(range, in: self.textView.text)

self.textView.scrollRangeToVisible(nsRange)
Swapnil Dhotre
  • 375
  • 4
  • 21