I was facing the same issue - basically there's a delay when becoming first responder that doesn't allow you to change selectedRange
in any of textView*BeginEditing:
methods. If you try to delay the setSelectedRange:
(let's say with performSelector:withObject:afterDelay:
) it shows ugly jerk.
The solution is actually pretty simple - checking order of delegate methods gives you the hint:
textViewShouldBeginEditing:
textViewDidBeginEditing:
textViewDidChangeSelection:
Setting selectedRange
in the last method (3) does the trick, you just need to make sure you reposition the cursor only for the first time when the UITextView
becomes first responder as the method (3) is called every time you update the content.
A BOOL variable set in shouldChangeTextInRange:
one of the methods (1), (2) and check for the variable in (3) should do the trick ... just don't forget to reset the variable after the reposition to avoid constant cursor reset :).
Hope it helps!
EDIT
After few rounds of testing I decided to set the BOOL flag in shouldChangeTextInRange:
instead of (2) or (3) as it proved to be more versatile. See my code:
@interface MyClass
{
/** A flag to determine whether caret should be positioned (YES - don't position caret; NO - move caret to beginning). */
BOOL _isContentGenerated;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// deleting
if([text length] == 0)
{
// deleting last character
if(range.length == [[textView text] length])
{
// reached beginning
/**
code to show placeholder and reset caret to the beginning
*/
_isContentGenerated = NO;
}
}
else
{
// adding
if(range.location == 0)
{
/**
code to hide placeholder
*/
_isContentGenerated = YES;
}
}
return YES;
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
if(!_isContentGenerated)
{
[textView setSelectedRange:NSMakeRange(0, 0)];
}
}