mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
58f69ca504
Summary: This is a more feature-full replacement for `textInputShouldChangeTextInRange:replacementText:` that allows implementing things like limiting text input content length without hacks and compromising the order of events. The new version of the TextInput will rely on that. The existing TextInput will use that as exacly like the previous version (because the new API is a superset of the the old one). Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D18670792 fbshipit-source-id: 0c434b351dfc8ab42404eb9aea0ede70bedaa9dc
287 lines
8.8 KiB
Objective-C
287 lines
8.8 KiB
Objective-C
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <React/RCTBackedTextInputDelegateAdapter.h>
|
|
|
|
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
|
|
|
|
static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingContext;
|
|
|
|
@interface RCTBackedTextFieldDelegateAdapter () <UITextFieldDelegate>
|
|
@end
|
|
|
|
@implementation RCTBackedTextFieldDelegateAdapter {
|
|
__weak UITextField<RCTBackedTextInputViewProtocol> *_backedTextInputView;
|
|
BOOL _textDidChangeIsComing;
|
|
UITextRange *_previousSelectedTextRange;
|
|
}
|
|
|
|
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInputView
|
|
{
|
|
if (self = [super init]) {
|
|
_backedTextInputView = backedTextInputView;
|
|
backedTextInputView.delegate = self;
|
|
|
|
[_backedTextInputView addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
|
|
[_backedTextInputView addTarget:self action:@selector(textFieldDidEndEditingOnExit) forControlEvents:UIControlEventEditingDidEndOnExit];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[_backedTextInputView removeTarget:self action:nil forControlEvents:UIControlEventEditingChanged];
|
|
[_backedTextInputView removeTarget:self action:nil forControlEvents:UIControlEventEditingDidEndOnExit];
|
|
}
|
|
|
|
#pragma mark - UITextFieldDelegate
|
|
|
|
- (BOOL)textFieldShouldBeginEditing:(__unused UITextField *)textField
|
|
{
|
|
return [_backedTextInputView.textInputDelegate textInputShouldBeginEditing];
|
|
}
|
|
|
|
- (void)textFieldDidBeginEditing:(__unused UITextField *)textField
|
|
{
|
|
[_backedTextInputView.textInputDelegate textInputDidBeginEditing];
|
|
}
|
|
|
|
- (BOOL)textFieldShouldEndEditing:(__unused UITextField *)textField
|
|
{
|
|
return [_backedTextInputView.textInputDelegate textInputShouldEndEditing];
|
|
}
|
|
|
|
- (void)textFieldDidEndEditing:(__unused UITextField *)textField
|
|
{
|
|
if (_textDidChangeIsComing) {
|
|
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
|
|
// which was triggered by losing focus. So, we call it manually.
|
|
_textDidChangeIsComing = NO;
|
|
[_backedTextInputView.textInputDelegate textInputDidChange];
|
|
}
|
|
|
|
[_backedTextInputView.textInputDelegate textInputDidEndEditing];
|
|
}
|
|
|
|
- (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
|
|
{
|
|
NSString *newText =
|
|
[_backedTextInputView.textInputDelegate textInputShouldChangeText:string inRange:range];
|
|
|
|
if (newText == nil) {
|
|
return NO;
|
|
}
|
|
|
|
if ([newText isEqualToString:string]) {
|
|
_textDidChangeIsComing = YES;
|
|
return YES;
|
|
}
|
|
|
|
NSMutableAttributedString *attributedString = [_backedTextInputView.attributedText mutableCopy];
|
|
[attributedString replaceCharactersInRange:range withString:newText];
|
|
[_backedTextInputView setAttributedText:[attributedString copy]];
|
|
|
|
// Setting selection to the end of the replaced text.
|
|
UITextPosition *position =
|
|
[_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
|
|
offset:(range.location + newText.length)];
|
|
[_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
|
|
notifyDelegate:YES];
|
|
|
|
[self textFieldDidChange];
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)textFieldShouldReturn:(__unused UITextField *)textField
|
|
{
|
|
return [_backedTextInputView.textInputDelegate textInputShouldReturn];
|
|
}
|
|
|
|
#pragma mark - UIControlEventEditing* Family Events
|
|
|
|
- (void)textFieldDidChange
|
|
{
|
|
_textDidChangeIsComing = NO;
|
|
[_backedTextInputView.textInputDelegate textInputDidChange];
|
|
|
|
// `selectedTextRangeWasSet` isn't triggered during typing.
|
|
[self textFieldProbablyDidChangeSelection];
|
|
}
|
|
|
|
- (void)textFieldDidEndEditingOnExit
|
|
{
|
|
[_backedTextInputView.textInputDelegate textInputDidReturn];
|
|
}
|
|
|
|
#pragma mark - UIKeyboardInput (private UIKit protocol)
|
|
|
|
// This method allows us to detect a [Backspace] `keyPress`
|
|
// even when there is no more text in the `UITextField`.
|
|
- (BOOL)keyboardInputShouldDelete:(__unused UITextField *)textField
|
|
{
|
|
[_backedTextInputView.textInputDelegate textInputShouldChangeText:@"" inRange:NSMakeRange(0, 0)];
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark - Public Interface
|
|
|
|
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange
|
|
{
|
|
_previousSelectedTextRange = textRange;
|
|
}
|
|
|
|
- (void)selectedTextRangeWasSet
|
|
{
|
|
[self textFieldProbablyDidChangeSelection];
|
|
}
|
|
|
|
#pragma mark - Generalization
|
|
|
|
- (void)textFieldProbablyDidChangeSelection
|
|
{
|
|
if ([_backedTextInputView.selectedTextRange isEqual:_previousSelectedTextRange]) {
|
|
return;
|
|
}
|
|
|
|
_previousSelectedTextRange = _backedTextInputView.selectedTextRange;
|
|
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
|
|
|
|
@interface RCTBackedTextViewDelegateAdapter () <UITextViewDelegate>
|
|
@end
|
|
|
|
@implementation RCTBackedTextViewDelegateAdapter {
|
|
__weak UITextView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
|
|
BOOL _textDidChangeIsComing;
|
|
UITextRange *_previousSelectedTextRange;
|
|
}
|
|
|
|
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView
|
|
{
|
|
if (self = [super init]) {
|
|
_backedTextInputView = backedTextInputView;
|
|
backedTextInputView.delegate = self;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - UITextViewDelegate
|
|
|
|
- (BOOL)textViewShouldBeginEditing:(__unused UITextView *)textView
|
|
{
|
|
return [_backedTextInputView.textInputDelegate textInputShouldBeginEditing];
|
|
}
|
|
|
|
- (void)textViewDidBeginEditing:(__unused UITextView *)textView
|
|
{
|
|
[_backedTextInputView.textInputDelegate textInputDidBeginEditing];
|
|
}
|
|
|
|
- (BOOL)textViewShouldEndEditing:(__unused UITextView *)textView
|
|
{
|
|
return [_backedTextInputView.textInputDelegate textInputShouldEndEditing];
|
|
}
|
|
|
|
- (void)textViewDidEndEditing:(__unused UITextView *)textView
|
|
{
|
|
if (_textDidChangeIsComing) {
|
|
// iOS does't call `textViewDidChange:` delegate method if the change was happened because of autocorrection
|
|
// which was triggered by losing focus. So, we call it manually.
|
|
_textDidChangeIsComing = NO;
|
|
[_backedTextInputView.textInputDelegate textInputDidChange];
|
|
}
|
|
|
|
[_backedTextInputView.textInputDelegate textInputDidEndEditing];
|
|
}
|
|
|
|
- (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
|
{
|
|
// Custom implementation of `textInputShouldReturn` and `textInputDidReturn` pair for `UITextView`.
|
|
if (!_backedTextInputView.textWasPasted && [text isEqualToString:@"\n"]) {
|
|
if ([_backedTextInputView.textInputDelegate textInputShouldReturn]) {
|
|
[_backedTextInputView.textInputDelegate textInputDidReturn];
|
|
[_backedTextInputView endEditing:NO];
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
NSString *newText =
|
|
[_backedTextInputView.textInputDelegate textInputShouldChangeText:text inRange:range];
|
|
|
|
if (newText == nil) {
|
|
return NO;
|
|
}
|
|
|
|
if ([newText isEqualToString:text]) {
|
|
_textDidChangeIsComing = YES;
|
|
return YES;
|
|
}
|
|
|
|
NSMutableAttributedString *attributedString = [_backedTextInputView.attributedText mutableCopy];
|
|
[attributedString replaceCharactersInRange:range withString:newText];
|
|
[_backedTextInputView setAttributedText:[attributedString copy]];
|
|
|
|
// Setting selection to the end of the replaced text.
|
|
UITextPosition *position =
|
|
[_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
|
|
offset:(range.location + newText.length)];
|
|
[_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
|
|
notifyDelegate:YES];
|
|
|
|
[self textViewDidChange:_backedTextInputView];
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void)textViewDidChange:(__unused UITextView *)textView
|
|
{
|
|
_textDidChangeIsComing = NO;
|
|
[_backedTextInputView.textInputDelegate textInputDidChange];
|
|
}
|
|
|
|
- (void)textViewDidChangeSelection:(__unused UITextView *)textView
|
|
{
|
|
[self textViewProbablyDidChangeSelection];
|
|
}
|
|
|
|
#pragma mark - UIScrollViewDelegate
|
|
|
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
{
|
|
if ([_backedTextInputView.textInputDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
|
|
[_backedTextInputView.textInputDelegate scrollViewDidScroll:scrollView];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Public Interface
|
|
|
|
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange
|
|
{
|
|
_previousSelectedTextRange = textRange;
|
|
}
|
|
|
|
#pragma mark - Generalization
|
|
|
|
- (void)textViewProbablyDidChangeSelection
|
|
{
|
|
if ([_backedTextInputView.selectedTextRange isEqual:_previousSelectedTextRange]) {
|
|
return;
|
|
}
|
|
|
|
_previousSelectedTextRange = _backedTextInputView.selectedTextRange;
|
|
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
|
|
}
|
|
|
|
@end
|