Skip to content

Commit 8219db9

Browse files
sherginfacebook-github-bot
authored andcommitted
Fabric: The basic implementation of <TextInput> for iOS
Summary: This is the partial implementation of Fabric-compatible <TextInput> component on iOS. All features are supported besides those: * `focus()`, `blur()`, `clear()` imperative calls; * Controlled TextInput as the whole feature in general; * Controlling selection from JavaScript side; * `autoFocus` prop; * KeyboardAccessoryView. Changelog: [Internal] Reviewed By: JoshuaGross Differential Revision: D17400907 fbshipit-source-id: 0ccd0e0923293e5f504d5fae7b7ba9f048f7d259
1 parent 4155796 commit 8219db9

File tree

3 files changed

+368
-0
lines changed

3 files changed

+368
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <UIKit/UIKit.h>
9+
10+
#import <React/RCTViewComponentView.h>
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
/**
15+
* UIView class for <TextInput> component.
16+
*/
17+
@interface RCTTextInputComponentView : RCTViewComponentView
18+
19+
@end
20+
21+
NS_ASSUME_NONNULL_END
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTTextInputComponentView.h"
9+
10+
#import <react/components/iostextinput/TextInputComponentDescriptor.h>
11+
#import <react/graphics/Geometry.h>
12+
#import <react/textlayoutmanager/RCTAttributedTextUtils.h>
13+
#import <react/textlayoutmanager/TextLayoutManager.h>
14+
15+
#import <React/RCTBackedTextInputViewProtocol.h>
16+
#import <React/RCTUITextField.h>
17+
#import <React/RCTUITextView.h>
18+
19+
#import "RCTConversions.h"
20+
#import "RCTTextInputUtils.h"
21+
22+
using namespace facebook::react;
23+
24+
@interface RCTTextInputComponentView () <RCTBackedTextInputDelegate>
25+
@end
26+
27+
@implementation RCTTextInputComponentView {
28+
TextInputShadowNode::ConcreteState::Shared _state;
29+
UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
30+
size_t _stateRevision;
31+
}
32+
33+
- (instancetype)initWithFrame:(CGRect)frame
34+
{
35+
if (self = [super initWithFrame:frame]) {
36+
static const auto defaultProps = std::make_shared<TextInputProps const>();
37+
_props = defaultProps;
38+
auto &props = *defaultProps;
39+
40+
_backedTextInputView = props.traits.multiline ? [[RCTUITextView alloc] init] : [[RCTUITextField alloc] init];
41+
_backedTextInputView.frame = self.bounds;
42+
_backedTextInputView.textInputDelegate = self;
43+
[self addSubview:_backedTextInputView];
44+
}
45+
46+
return self;
47+
}
48+
49+
#pragma mark - RCTComponentViewProtocol
50+
51+
+ (ComponentDescriptorProvider)componentDescriptorProvider
52+
{
53+
return concreteComponentDescriptorProvider<TextInputComponentDescriptor>();
54+
}
55+
56+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
57+
{
58+
auto const &oldTextInputProps = *std::static_pointer_cast<TextInputProps const>(_props);
59+
auto const &newTextInputProps = *std::static_pointer_cast<TextInputProps const>(props);
60+
61+
// Traits:
62+
if (newTextInputProps.traits.multiline != oldTextInputProps.traits.multiline) {
63+
[self _setMultiline:newTextInputProps.traits.multiline];
64+
}
65+
66+
if (newTextInputProps.traits.autocapitalizationType != oldTextInputProps.traits.autocapitalizationType) {
67+
_backedTextInputView.autocapitalizationType =
68+
RCTUITextAutocapitalizationTypeFromAutocapitalizationType(newTextInputProps.traits.autocapitalizationType);
69+
}
70+
71+
if (newTextInputProps.traits.autoCorrect != oldTextInputProps.traits.autoCorrect) {
72+
_backedTextInputView.autocorrectionType =
73+
RCTUITextAutocorrectionTypeFromOptionalBool(newTextInputProps.traits.autoCorrect);
74+
}
75+
76+
if (newTextInputProps.traits.contextMenuHidden != oldTextInputProps.traits.contextMenuHidden) {
77+
_backedTextInputView.contextMenuHidden = newTextInputProps.traits.contextMenuHidden;
78+
}
79+
80+
if (newTextInputProps.traits.editable != oldTextInputProps.traits.editable) {
81+
_backedTextInputView.editable = newTextInputProps.traits.editable;
82+
}
83+
84+
if (newTextInputProps.traits.enablesReturnKeyAutomatically !=
85+
oldTextInputProps.traits.enablesReturnKeyAutomatically) {
86+
_backedTextInputView.enablesReturnKeyAutomatically = newTextInputProps.traits.enablesReturnKeyAutomatically;
87+
}
88+
89+
if (newTextInputProps.traits.keyboardAppearance != oldTextInputProps.traits.keyboardAppearance) {
90+
_backedTextInputView.keyboardAppearance =
91+
RCTUIKeyboardAppearanceFromKeyboardAppearance(newTextInputProps.traits.keyboardAppearance);
92+
}
93+
94+
if (newTextInputProps.traits.spellCheck != oldTextInputProps.traits.spellCheck) {
95+
_backedTextInputView.spellCheckingType =
96+
RCTUITextSpellCheckingTypeFromOptionalBool(newTextInputProps.traits.spellCheck);
97+
}
98+
99+
if (newTextInputProps.traits.caretHidden != oldTextInputProps.traits.caretHidden) {
100+
_backedTextInputView.caretHidden = newTextInputProps.traits.caretHidden;
101+
}
102+
103+
if (newTextInputProps.traits.clearButtonMode != oldTextInputProps.traits.clearButtonMode) {
104+
_backedTextInputView.clearButtonMode =
105+
RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode(newTextInputProps.traits.clearButtonMode);
106+
}
107+
108+
if (newTextInputProps.traits.scrollEnabled != oldTextInputProps.traits.scrollEnabled) {
109+
_backedTextInputView.scrollEnabled = newTextInputProps.traits.scrollEnabled;
110+
}
111+
112+
if (newTextInputProps.traits.secureTextEntry != oldTextInputProps.traits.secureTextEntry) {
113+
_backedTextInputView.secureTextEntry = newTextInputProps.traits.secureTextEntry;
114+
}
115+
116+
if (newTextInputProps.traits.keyboardType != oldTextInputProps.traits.keyboardType) {
117+
_backedTextInputView.keyboardType = RCTUIKeyboardTypeFromKeyboardType(newTextInputProps.traits.keyboardType);
118+
}
119+
120+
if (newTextInputProps.traits.returnKeyType != oldTextInputProps.traits.returnKeyType) {
121+
_backedTextInputView.returnKeyType = RCTUIReturnKeyTypeFromReturnKeyType(newTextInputProps.traits.returnKeyType);
122+
}
123+
124+
if (newTextInputProps.traits.textContentType != oldTextInputProps.traits.textContentType) {
125+
if (@available(iOS 10.0, *)) {
126+
_backedTextInputView.textContentType = RCTUITextContentTypeFromString(newTextInputProps.traits.textContentType);
127+
}
128+
}
129+
130+
if (newTextInputProps.traits.passwordRules != oldTextInputProps.traits.passwordRules) {
131+
if (@available(iOS 12.0, *)) {
132+
_backedTextInputView.passwordRules =
133+
RCTUITextInputPasswordRulesFromString(newTextInputProps.traits.passwordRules);
134+
}
135+
}
136+
137+
// Traits `blurOnSubmit`, `clearTextOnFocus`, and `selectTextOnFocus` were omitted intentially here
138+
// because they are being checked on-demand.
139+
140+
// Other props:
141+
if (newTextInputProps.placeholder != oldTextInputProps.placeholder) {
142+
_backedTextInputView.placeholder = RCTNSStringFromString(newTextInputProps.placeholder);
143+
}
144+
145+
if (newTextInputProps.textAttributes != oldTextInputProps.textAttributes) {
146+
_backedTextInputView.defaultTextAttributes =
147+
RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes());
148+
}
149+
150+
if (newTextInputProps.selectionColor != oldTextInputProps.selectionColor) {
151+
_backedTextInputView.tintColor = RCTUIColorFromSharedColor(newTextInputProps.selectionColor);
152+
}
153+
154+
[super updateProps:props oldProps:oldProps];
155+
}
156+
157+
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState
158+
{
159+
_state = std::static_pointer_cast<TextInputShadowNode::ConcreteState const>(state);
160+
161+
if (!_state) {
162+
assert(false && "State is `null` for <TextInput> component.");
163+
_backedTextInputView.attributedText = nil;
164+
return;
165+
}
166+
167+
auto data = _state->getData();
168+
169+
if (data.revision != _stateRevision) {
170+
_stateRevision = data.revision;
171+
_backedTextInputView.attributedText = RCTNSAttributedStringFromAttributedStringBox(data.attributedStringBox);
172+
}
173+
}
174+
175+
- (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics
176+
oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics
177+
{
178+
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
179+
180+
_backedTextInputView.frame =
181+
UIEdgeInsetsInsetRect(self.bounds, RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.borderWidth));
182+
_backedTextInputView.textContainerInset =
183+
RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.contentInsets - layoutMetrics.borderWidth);
184+
}
185+
186+
- (void)prepareForRecycle
187+
{
188+
[super prepareForRecycle];
189+
_backedTextInputView.attributedText = [[NSAttributedString alloc] init];
190+
_state.reset();
191+
_stateRevision = 0;
192+
}
193+
194+
#pragma mark - RCTComponentViewProtocol
195+
196+
- (void)_setMultiline:(BOOL)multiline
197+
{
198+
[_backedTextInputView removeFromSuperview];
199+
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView =
200+
multiline ? [[RCTUITextView alloc] init] : [[RCTUITextField alloc] init];
201+
backedTextInputView.frame = _backedTextInputView.frame;
202+
RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView);
203+
_backedTextInputView = backedTextInputView;
204+
[self addSubview:_backedTextInputView];
205+
}
206+
207+
#pragma mark - RCTBackedTextInputDelegate
208+
209+
- (BOOL)textInputShouldBeginEditing
210+
{
211+
return YES;
212+
}
213+
214+
- (void)textInputDidBeginEditing
215+
{
216+
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
217+
218+
if (props.traits.clearTextOnFocus) {
219+
_backedTextInputView.attributedText = [NSAttributedString new];
220+
[self textInputDidChange];
221+
}
222+
223+
if (props.traits.selectTextOnFocus) {
224+
[_backedTextInputView selectAll:nil];
225+
[self textInputDidChangeSelection];
226+
}
227+
228+
if (_eventEmitter) {
229+
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onFocus([self _textInputMetrics]);
230+
}
231+
}
232+
233+
- (BOOL)textInputShouldEndEditing
234+
{
235+
return YES;
236+
}
237+
238+
- (void)textInputDidEndEditing
239+
{
240+
if (_eventEmitter) {
241+
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onEndEditing([self _textInputMetrics]);
242+
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onBlur([self _textInputMetrics]);
243+
}
244+
}
245+
246+
- (BOOL)textInputShouldReturn
247+
{
248+
// We send `submit` event here, in `textInputShouldReturn`
249+
// (not in `textInputDidReturn)`, because of semantic of the event:
250+
// `onSubmitEditing` is called when "Submit" button
251+
// (the blue key on onscreen keyboard) did pressed
252+
// (no connection to any specific "submitting" process).
253+
254+
if (_eventEmitter) {
255+
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onSubmitEditing([self _textInputMetrics]);
256+
}
257+
258+
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
259+
return props.traits.blurOnSubmit;
260+
}
261+
262+
- (void)textInputDidReturn
263+
{
264+
// Does nothing.
265+
}
266+
267+
- (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range
268+
{
269+
if (!_backedTextInputView.textWasPasted) {
270+
if (_eventEmitter) {
271+
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onKeyPress([self _textInputMetrics]);
272+
}
273+
}
274+
275+
auto const &props = *std::static_pointer_cast<TextInputProps const>(_props);
276+
if (props.maxLength) {
277+
NSInteger allowedLength = props.maxLength - _backedTextInputView.attributedText.string.length + range.length;
278+
279+
if (allowedLength <= 0) {
280+
return nil;
281+
}
282+
283+
return allowedLength > text.length ? text : [text substringToIndex:allowedLength];
284+
}
285+
286+
return text;
287+
}
288+
289+
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
290+
{
291+
return YES;
292+
}
293+
294+
- (void)textInputDidChange
295+
{
296+
[self _updateState];
297+
298+
if (_eventEmitter) {
299+
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onChange([self _textInputMetrics]);
300+
}
301+
}
302+
303+
- (void)textInputDidChangeSelection
304+
{
305+
if (_eventEmitter) {
306+
std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter)->onSelectionChange([self _textInputMetrics]);
307+
}
308+
}
309+
310+
#pragma mark - Other
311+
312+
- (TextInputMetrics)_textInputMetrics
313+
{
314+
TextInputMetrics metrics;
315+
metrics.text = RCTStringFromNSString(_backedTextInputView.attributedText.string);
316+
metrics.selectionRange = [self _selectionRange];
317+
return metrics;
318+
}
319+
320+
- (void)_updateState
321+
{
322+
NSAttributedString *attributedString = _backedTextInputView.attributedText;
323+
324+
if (!_state) {
325+
return;
326+
}
327+
328+
auto data = _state->getData();
329+
data.revision++;
330+
_stateRevision = data.revision;
331+
data.attributedStringBox = RCTAttributedStringBoxFromNSAttributedString(attributedString);
332+
_state->updateState(std::move(data), EventPriority::SynchronousUnbatched);
333+
}
334+
335+
- (AttributedString::Range)_selectionRange
336+
{
337+
UITextRange *selectedTextRange = _backedTextInputView.selectedTextRange;
338+
NSInteger start = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
339+
toPosition:selectedTextRange.start];
340+
NSInteger end = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
341+
toPosition:selectedTextRange.end];
342+
return AttributedString::Range{(int)start, (int)(end - start)};
343+
}
344+
345+
@end

React/Fabric/Mounting/RCTComponentViewFactory.mm

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#import "RCTMountingTransactionObserving.h"
3131
#import "RCTParagraphComponentView.h"
3232
#import "RCTRootComponentView.h"
33+
#import "RCTTextInputComponentView.h"
3334
#import "RCTUnimplementedViewComponentView.h"
3435
#import "RCTViewComponentView.h"
3536

@@ -55,6 +56,7 @@ + (RCTComponentViewFactory *)standardComponentViewFactory
5556
[componentViewFactory registerComponentViewClass:[RCTRootComponentView class]];
5657
[componentViewFactory registerComponentViewClass:[RCTViewComponentView class]];
5758
[componentViewFactory registerComponentViewClass:[RCTParagraphComponentView class]];
59+
[componentViewFactory registerComponentViewClass:[RCTTextInputComponentView class]];
5860

5961
Class<RCTComponentViewProtocol> imageClass = RCTComponentViewClassWithName("Image");
6062
if (imageClass) {

0 commit comments

Comments
 (0)