3

I try to get the UITextView caret position. For this, I use caretRectForPosition method. It works fine while typing text manually. But if I insert text into the text view, the method returns nonsensical negative coordinate.

Here is subject part of my code:

- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {

        // Truncated part of the code: text preparation, objects declaration and so on.

        // Past calculated text into the textView
        textView.text = newTextViewText;

        // Calculate cursor position to avoid its jump to the end of the string. This part works fine.
        NSInteger cursorPosition = range.location + allowedText.length;
        textView.selectedRange = NSMakeRange(cursorPosition, 0);

        // Try to get caret coordinates. It doesn't work properly when text is pasted
        cursorCoordinates = [textView caretRectForPosition:textView.selectedTextRange.end].origin;

    }

I suppose there is some delay after text insert and the string is been processed when I try to get the cursor coordinates. But I have no Idea where to look for this time gap source. Any Idea?

Update: I found out that this occurs when the inserting text is placed in 2 and more lines. Still don't know how to fix it.

Anton Lovchikov
  • 473
  • 1
  • 3
  • 13

2 Answers2

2

I've got the same problem... and yes, it seems to be a timing problem. My solution is:

A: Detect the invalid result from caretRectForPosition. In my case, the invalid coordinates seem always to be either large negative values (-1.0 seems to be i.O.!) or 'infinite' for origin.y.

B: Re-ask the text view for the caret position after a short period of time. I checked a few values for delay; 0.05 seems to be largely enough.

The code:

- (void)textViewDidChange:(UITextView *)pTextView {

    UITextPosition* endPos = pTextView.selectedTextRange.end;

    CGRect          caretRectInTextView = [pTextView caretRectForPosition:endPos];

    if ((-1.0 > CGRectGetMinY(caretRectInTextView)) ||
        (INFINITY == CGRectGetMinY(caretRectInTextView))) {
        NSLog(@"Invalid caretRectInTextView detected!");

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
                       dispatch_get_main_queue(),
                       ^{
                           // Recall
                           [self textViewDidChange:pTextView];
                        });
        return;


    }

    ... your code ...
}
LaborEtArs
  • 1,938
  • 23
  • 27
  • Thanks, this worked for our case as well, it's also possible to use more classic API `[self performSelector:@selector(textViewDidChange:) withObject:pTextView afterDelay:0.05];` – zubko Feb 05 '19 at 07:12
0

So, you are right. You have to use: - (void)textViewDidChangeSelection:(UITextView *)textView I thing it will be call last one. Just after that you will have the current position of caret.