141 lines
4.9 KiB
Objective-C
141 lines
4.9 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTTextField.h"
|
|
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTEventDispatcher.h>
|
|
#import <React/RCTUIManager.h>
|
|
#import <React/RCTUtils.h>
|
|
#import <React/UIView+React.h>
|
|
|
|
#import "RCTBackedTextInputDelegate.h"
|
|
#import "RCTTextSelection.h"
|
|
#import "RCTUITextField.h"
|
|
|
|
@interface RCTTextField () <RCTBackedTextInputDelegate>
|
|
|
|
@end
|
|
|
|
@implementation RCTTextField
|
|
{
|
|
RCTUITextField *_backedTextInput;
|
|
BOOL _submitted;
|
|
CGSize _previousContentSize;
|
|
}
|
|
|
|
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
|
{
|
|
if (self = [super initWithBridge:bridge]) {
|
|
// `blurOnSubmit` defaults to `true` for <TextInput multiline={false}> by design.
|
|
_blurOnSubmit = YES;
|
|
|
|
_backedTextInput = [[RCTUITextField alloc] initWithFrame:self.bounds];
|
|
_backedTextInput.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
_backedTextInput.textInputDelegate = self;
|
|
|
|
[self addSubview:_backedTextInput];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|
|
|
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
|
|
{
|
|
return _backedTextInput;
|
|
}
|
|
|
|
- (void)sendKeyValueForString:(NSString *)string
|
|
{
|
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
|
|
reactTag:self.reactTag
|
|
text:nil
|
|
key:string
|
|
eventCount:_nativeEventCount];
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
- (NSString *)text
|
|
{
|
|
return _backedTextInput.text;
|
|
}
|
|
|
|
- (void)setText:(NSString *)text
|
|
{
|
|
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
|
if (eventLag == 0 && ![text isEqualToString:self.text]) {
|
|
UITextRange *selection = _backedTextInput.selectedTextRange;
|
|
NSInteger oldTextLength = _backedTextInput.text.length;
|
|
|
|
_backedTextInput.text = text;
|
|
|
|
if (selection.empty) {
|
|
// maintain cursor position relative to the end of the old text
|
|
NSInteger offsetStart = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
|
|
NSInteger offsetFromEnd = oldTextLength - offsetStart;
|
|
NSInteger newOffset = text.length - offsetFromEnd;
|
|
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
|
|
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
|
|
notifyDelegate:YES];
|
|
}
|
|
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
|
RCTLogWarn(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", _backedTextInput.text, (long long)eventLag);
|
|
}
|
|
}
|
|
|
|
#pragma mark - RCTBackedTextInputDelegate
|
|
|
|
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string
|
|
{
|
|
// Only allow single keypresses for `onKeyPress`, pasted text will not be sent.
|
|
if (!_backedTextInput.textWasPasted) {
|
|
[self sendKeyValueForString:string];
|
|
}
|
|
|
|
if (_maxLength != nil && ![string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return.
|
|
NSUInteger allowedLength = _maxLength.integerValue - MIN(_maxLength.integerValue, _backedTextInput.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 = _backedTextInput.text.mutableCopy;
|
|
[newString replaceCharactersInRange:range withString:limitedString];
|
|
_backedTextInput.text = newString;
|
|
|
|
// Collapse selection at end of insert to match normal paste behavior.
|
|
UITextPosition *insertEnd = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
|
|
offset:(range.location + allowedLength)];
|
|
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:insertEnd toPosition:insertEnd]
|
|
notifyDelegate:YES];
|
|
[self textInputDidChange];
|
|
}
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)textInputDidChange
|
|
{
|
|
_nativeEventCount++;
|
|
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
|
reactTag:self.reactTag
|
|
text:_backedTextInput.text
|
|
key:nil
|
|
eventCount:_nativeEventCount];
|
|
}
|
|
|
|
@end
|