Permalink
Browse files

Maintain cursor position when TextInput value is changed programmatic…

…ally

Summary:
This is useful for applying input masks in the onChange handler that you then need to propagate down to the native component. In our case, we add commas as the user enters a price. Without this change, the cursor will end up in the wrong place when the text is transformed in our onChange handler.
Closes #4716

Reviewed By: svcscm

Differential Revision: D2766236

Pulled By: nicklockwood

fb-gh-sync-id: c4057d77d62507ec9e09eb0242888bf2858d822f
  • Loading branch information...
cava23 authored and facebook-github-bot-1 committed Dec 17, 2015
1 parent 7f2940e commit fe86771a229373030cddca421b92c7b1df5ee106
Showing with 38 additions and 6 deletions.
  1. +11 −1 Libraries/Text/RCTTextField.m
  2. +27 −5 Libraries/Text/RCTTextView.m
@@ -71,8 +71,18 @@ - (void)setText:(NSString *)text
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
if (eventLag == 0 && ![text isEqualToString:self.text]) {
UITextRange *selection = self.selectedTextRange;
NSInteger oldTextLength = self.text.length;
super.text = text;
self.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds
if (selection.empty) {
// maintain cursor position relative to the end of the old text
NSInteger offsetStart = [self offsetFromPosition:self.beginningOfDocument toPosition:selection.start];
NSInteger offsetFromEnd = oldTextLength - offsetStart;
NSInteger newOffset = text.length - offsetFromEnd;
UITextPosition *position = [self positionFromPosition:self.beginningOfDocument offset:newOffset];
self.selectedTextRange = [self textRangeFromPosition:position toPosition:position];
}
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", self.text, eventLag);
}
@@ -163,10 +163,21 @@ - (void)performPendingTextUpdate
// we temporarily block all textShouldChange events so they are not applied.
_blockTextShouldChange = YES;
NSRange range = _textView.selectedRange;
UITextRange *selection = _textView.selectedTextRange;
NSInteger oldTextLength = _textView.attributedText.length;
_textView.attributedText = _pendingAttributedText;
_pendingAttributedText = nil;
_textView.selectedRange = range;
if (selection.empty) {
// maintain cursor position relative to the end of the old text
NSInteger start = [_textView offsetFromPosition:_textView.beginningOfDocument toPosition:selection.start];
NSInteger offsetFromEnd = oldTextLength - start;
NSInteger newOffset = _textView.attributedText.length - offsetFromEnd;
UITextPosition *position = [_textView positionFromPosition:_textView.beginningOfDocument offset:newOffset];
_textView.selectedTextRange = [_textView textRangeFromPosition:position toPosition:position];
}
[_textView layoutIfNeeded];
[self _setPlaceholderVisibility];
@@ -341,8 +352,8 @@ - (void)textViewDidChangeSelection:(RCTUITextView *)textView
_previousSelectionRange = textView.selectedTextRange;
UITextRange *selection = textView.selectedTextRange;
NSInteger start = [textView offsetFromPosition:[textView beginningOfDocument] toPosition:selection.start];
NSInteger end = [textView offsetFromPosition:[textView beginningOfDocument] toPosition:selection.end];
NSInteger start = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selection.start];
NSInteger end = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selection.end];
_onSelectionChange(@{
@"selection": @{
@"start": @(start),
@@ -357,9 +368,20 @@ - (void)setText:(NSString *)text
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
if (eventLag == 0 && ![text isEqualToString:_textView.text]) {
UITextRange *selection = _textView.selectedTextRange;
NSInteger oldTextLength = _textView.text.length;
_textView.text = text;
if (selection.empty) {
// maintain cursor position relative to the end of the old text
NSInteger start = [_textView offsetFromPosition:_textView.beginningOfDocument toPosition:selection.start];
NSInteger offsetFromEnd = oldTextLength - start;
NSInteger newOffset = text.length - offsetFromEnd;
UITextPosition *position = [_textView positionFromPosition:_textView.beginningOfDocument offset:newOffset];
_textView.selectedTextRange = [_textView textRangeFromPosition:position toPosition:position];
}
[self _setPlaceholderVisibility];
_textView.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", self.text, eventLag);
}

0 comments on commit fe86771

Please sign in to comment.