Fixed calling TextInput.onChange() on applying autocorrection (iOS), Second part
Reviewed By: mmmulani Differential Revision: D4459603 fbshipit-source-id: f1ee25a9068213a54f2c088760297ba9c2838623
This commit is contained in:
parent
a3f86ae984
commit
a341e9d916
|
@ -22,14 +22,9 @@
|
||||||
@property (nonatomic, strong) UIColor *placeholderTextColor;
|
@property (nonatomic, strong) UIColor *placeholderTextColor;
|
||||||
@property (nonatomic, assign) NSInteger mostRecentEventCount;
|
@property (nonatomic, assign) NSInteger mostRecentEventCount;
|
||||||
@property (nonatomic, strong) NSNumber *maxLength;
|
@property (nonatomic, strong) NSNumber *maxLength;
|
||||||
@property (nonatomic, assign) BOOL textWasPasted;
|
|
||||||
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
|
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
|
||||||
|
|
||||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
- (void)textFieldDidChange;
|
|
||||||
- (void)sendKeyValueForString:(NSString *)string;
|
|
||||||
- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -16,6 +16,35 @@
|
||||||
|
|
||||||
#import "RCTTextSelection.h"
|
#import "RCTTextSelection.h"
|
||||||
|
|
||||||
|
@interface RCTTextField()
|
||||||
|
|
||||||
|
- (BOOL)shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
|
||||||
|
- (BOOL)keyboardInputShouldDelete;
|
||||||
|
- (BOOL)textFieldShouldEndEditing;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface RCTTextFieldDelegateProxy: NSObject <UITextFieldDelegate>
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation RCTTextFieldDelegateProxy
|
||||||
|
|
||||||
|
- (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
||||||
|
{
|
||||||
|
return [textField shouldChangeCharactersInRange:range replacementString:string];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)keyboardInputShouldDelete:(RCTTextField *)textField
|
||||||
|
{
|
||||||
|
return [textField keyboardInputShouldDelete];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField {
|
||||||
|
return [textField textFieldShouldEndEditing];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation RCTTextField
|
@implementation RCTTextField
|
||||||
{
|
{
|
||||||
RCTEventDispatcher *_eventDispatcher;
|
RCTEventDispatcher *_eventDispatcher;
|
||||||
|
@ -23,6 +52,9 @@
|
||||||
NSInteger _nativeEventCount;
|
NSInteger _nativeEventCount;
|
||||||
BOOL _submitted;
|
BOOL _submitted;
|
||||||
UITextRange *_previousSelectionRange;
|
UITextRange *_previousSelectionRange;
|
||||||
|
BOOL _textWasPasted;
|
||||||
|
NSString *_finalText;
|
||||||
|
RCTTextFieldDelegateProxy *_delegateProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||||
|
@ -36,6 +68,11 @@
|
||||||
[self addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
|
[self addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
|
||||||
[self addObserver:self forKeyPath:@"selectedTextRange" options:0 context:nil];
|
[self addObserver:self forKeyPath:@"selectedTextRange" options:0 context:nil];
|
||||||
_blurOnSubmit = YES;
|
_blurOnSubmit = YES;
|
||||||
|
|
||||||
|
// We cannot use `self.delegate = self;` here because `UITextField` implements some of these delegate methods itself,
|
||||||
|
// so if we implement this delegate on self, we will override some of its behaviours.
|
||||||
|
_delegateProxy = [RCTTextFieldDelegateProxy new];
|
||||||
|
self.delegate = _delegateProxy;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -176,6 +213,14 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
|
|
||||||
- (void)textFieldEndEditing
|
- (void)textFieldEndEditing
|
||||||
{
|
{
|
||||||
|
if (![_finalText isEqualToString:self.text]) {
|
||||||
|
_finalText = nil;
|
||||||
|
// iOS does't send event `UIControlEventEditingChanged` if the change was happened because of autocorrection
|
||||||
|
// which was triggered by loosing focus. We assume that if `text` was changed in the middle of loosing focus process,
|
||||||
|
// we did not receive that event. So, we call `textFieldDidChange` manually.
|
||||||
|
[self textFieldDidChange];
|
||||||
|
}
|
||||||
|
|
||||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||||
reactTag:self.reactTag
|
reactTag:self.reactTag
|
||||||
text:self.text
|
text:self.text
|
||||||
|
@ -209,15 +254,6 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField
|
|
||||||
{
|
|
||||||
if (_submitted) {
|
|
||||||
_submitted = NO;
|
|
||||||
return _blurOnSubmit;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||||
ofObject:(RCTTextField *)textField
|
ofObject:(RCTTextField *)textField
|
||||||
change:(NSDictionary *)change
|
change:(NSDictionary *)change
|
||||||
|
@ -277,4 +313,58 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITextFieldDelegate (Proxied)
|
||||||
|
|
||||||
|
- (BOOL)shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
||||||
|
{
|
||||||
|
// Only allow single keypresses for `onKeyPress`, pasted text will not be sent.
|
||||||
|
if (_textWasPasted) {
|
||||||
|
_textWasPasted = NO;
|
||||||
|
} else {
|
||||||
|
[self sendKeyValueForString:string];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_maxLength != nil && ![string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return.
|
||||||
|
NSUInteger allowedLength = _maxLength.integerValue - MIN(_maxLength.integerValue, self.text.length) + range.length;
|
||||||
|
if (string.length > allowedLength) {
|
||||||
|
if (string.length > 1) {
|
||||||
|
// Truncate the input string so the result is exactly `maxLength`.
|
||||||
|
NSString *limitedString = [string substringToIndex:allowedLength];
|
||||||
|
NSMutableString *newString = self.text.mutableCopy;
|
||||||
|
[newString replaceCharactersInRange:range withString:limitedString];
|
||||||
|
self.text = newString;
|
||||||
|
|
||||||
|
// Collapse selection at end of insert to match normal paste behavior.
|
||||||
|
UITextPosition *insertEnd = [self positionFromPosition:self.beginningOfDocument
|
||||||
|
offset:(range.location + allowedLength)];
|
||||||
|
self.selectedTextRange = [self textRangeFromPosition:insertEnd toPosition:insertEnd];
|
||||||
|
[self textFieldDidChange];
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method allows us to detect a `Backspace` keyPress
|
||||||
|
// even when there is no more text in the TextField.
|
||||||
|
- (BOOL)keyboardInputShouldDelete
|
||||||
|
{
|
||||||
|
[self shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)textFieldShouldEndEditing
|
||||||
|
{
|
||||||
|
_finalText = self.text;
|
||||||
|
|
||||||
|
if (_submitted) {
|
||||||
|
_submitted = NO;
|
||||||
|
return _blurOnSubmit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -13,12 +13,9 @@
|
||||||
#import <React/RCTFont.h>
|
#import <React/RCTFont.h>
|
||||||
#import <React/RCTShadowView.h>
|
#import <React/RCTShadowView.h>
|
||||||
|
|
||||||
#import "RCTTextField.h"
|
|
||||||
#import "RCTConvert+Text.h"
|
#import "RCTConvert+Text.h"
|
||||||
|
#import "RCTTextField.h"
|
||||||
|
|
||||||
@interface RCTTextFieldManager() <UITextFieldDelegate>
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation RCTTextFieldManager
|
@implementation RCTTextFieldManager
|
||||||
|
|
||||||
|
@ -26,54 +23,7 @@ RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
- (UIView *)view
|
- (UIView *)view
|
||||||
{
|
{
|
||||||
RCTTextField *textField = [[RCTTextField alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
return [[RCTTextField alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
||||||
textField.delegate = self;
|
|
||||||
return textField;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
|
||||||
{
|
|
||||||
// Only allow single keypresses for onKeyPress, pasted text will not be sent.
|
|
||||||
if (textField.textWasPasted) {
|
|
||||||
textField.textWasPasted = NO;
|
|
||||||
} else {
|
|
||||||
[textField sendKeyValueForString:string];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textField.maxLength == nil || [string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
NSUInteger allowedLength = textField.maxLength.integerValue - MIN(textField.maxLength.integerValue, textField.text.length) + range.length;
|
|
||||||
if (string.length > allowedLength) {
|
|
||||||
if (string.length > 1) {
|
|
||||||
// Truncate the input string so the result is exactly maxLength
|
|
||||||
NSString *limitedString = [string substringToIndex:allowedLength];
|
|
||||||
NSMutableString *newString = textField.text.mutableCopy;
|
|
||||||
[newString replaceCharactersInRange:range withString:limitedString];
|
|
||||||
textField.text = newString;
|
|
||||||
// Collapse selection at end of insert to match normal paste behavior
|
|
||||||
UITextPosition *insertEnd = [textField positionFromPosition:textField.beginningOfDocument
|
|
||||||
offset:(range.location + allowedLength)];
|
|
||||||
textField.selectedTextRange = [textField textRangeFromPosition:insertEnd toPosition:insertEnd];
|
|
||||||
[textField textFieldDidChange];
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
} else {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method allows us to detect a `Backspace` keyPress
|
|
||||||
// even when there is no more text in the TextField
|
|
||||||
- (BOOL)keyboardInputShouldDelete:(RCTTextField *)textField
|
|
||||||
{
|
|
||||||
[self textField:textField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField
|
|
||||||
{
|
|
||||||
return [textField textFieldShouldEndEditing:textField];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL)
|
RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL)
|
||||||
|
|
Loading…
Reference in New Issue