mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-05-07 13:05:18 +08:00
[ReactNative] TextInput bug fixes and features
Summary: This introduces event counts to make sure JS doesn't set out of date values on native text inputs, which can cause dropped characters and can mess with autocomplete, and obviates the need for the input buffering which added lag and complexity to the component. Made sure to test simulated super-slow JS text event processing to make sure characters aren't dropped, as well as typing obviously correctable words and making sure autocomplete works as expected. TextInput is now a controlled input by default without causing any issues for most cases, so I removed the `controlled` prop. Fixes selection state jumping by restoring it after setting new text values, so highlighting the middle of some text in the new ReWrite example and hitting space will replace that selection with an underscore and keep the cursor at a sensible position as expected, instead of jumping to the end. Ads `maxLength` prop to support the most commonly needed syncronous behavior: preventing the user from typing too many characters. It can also be used to prevent users from continuing to type after entering special characters by changing it to the current length after a regex match. Made sure to verify it works well with pasted input (including in the middle of existing text), truncating it and collapsing the selection the same way it does on the web. Fixes bug in TextEventsExample where it wouldn't show the submit and end events, even though there were firing correctly.
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
NSString *_placeholder;
|
||||
UITextView *_placeholderView;
|
||||
UITextView *_textView;
|
||||
NSInteger _nativeEventCount;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
@@ -124,11 +125,41 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||
return _textView.text;
|
||||
}
|
||||
|
||||
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
||||
{
|
||||
if (_maxLength == nil) {
|
||||
return YES;
|
||||
}
|
||||
NSUInteger allowedLength = _maxLength.integerValue - textView.text.length + range.length;
|
||||
if (text.length > allowedLength) {
|
||||
if (text.length > 1) {
|
||||
// Truncate the input string so the result is exactly maxLength
|
||||
NSString *limitedString = [text substringToIndex:allowedLength];
|
||||
NSMutableString *newString = textView.text.mutableCopy;
|
||||
[newString replaceCharactersInRange:range withString:limitedString];
|
||||
textView.text = newString;
|
||||
// Collapse selection at end of insert to match normal paste behavior
|
||||
UITextPosition *insertEnd = [textView positionFromPosition:textView.beginningOfDocument
|
||||
offset:(range.location + allowedLength)];
|
||||
textView.selectedTextRange = [textView textRangeFromPosition:insertEnd toPosition:insertEnd];
|
||||
[self textViewDidChange:textView];
|
||||
}
|
||||
return NO;
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
if (![text isEqualToString:_textView.text]) {
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![text isEqualToString:_textView.text]) {
|
||||
UITextRange *selection = _textView.selectedTextRange;
|
||||
[_textView setText:text];
|
||||
[self _setPlaceholderVisibility];
|
||||
_textView.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLogWarn(@"Native TextInput(%@) is %ld events ahead of JS - try to make your JS faster.", self.text, (long)eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,15 +201,18 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||
reactTag:self.reactTag
|
||||
text:textView.text];
|
||||
text:textView.text
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)textView
|
||||
{
|
||||
[self _setPlaceholderVisibility];
|
||||
_nativeEventCount++;
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||
reactTag:self.reactTag
|
||||
text:textView.text];
|
||||
text:textView.text
|
||||
eventCount:_nativeEventCount];
|
||||
|
||||
}
|
||||
|
||||
@@ -186,7 +220,8 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||
reactTag:self.reactTag
|
||||
text:textView.text];
|
||||
text:textView.text
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
@@ -204,7 +239,8 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||
if (result) {
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
|
||||
reactTag:self.reactTag
|
||||
text:_textView.text];
|
||||
text:_textView.text
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user