1111
1212#import < React/RCTConvert.h>
1313#import < React/RCTEventDispatcher.h>
14+ #import < React/RCTUIManager.h>
1415#import < React/RCTUtils.h>
1516#import < React/UIView+React.h>
1617
@@ -69,6 +70,7 @@ - (void)setContentOffset:(CGPoint)contentOffset animated:(__unused BOOL)animated
6970
7071@implementation RCTTextView
7172{
73+ RCTBridge *_bridge;
7274 RCTEventDispatcher *_eventDispatcher;
7375
7476 NSString *_placeholder;
@@ -87,22 +89,25 @@ @implementation RCTTextView
8789 NSInteger _nativeEventCount;
8890
8991 CGSize _previousContentSize;
90- BOOL _viewDidCompleteInitialLayout;
9192}
9293
93- - (instancetype )initWithEventDispatcher : (RCTEventDispatcher *)eventDispatcher
94+ - (instancetype )initWithBridge : (RCTBridge *)bridge
9495{
95- RCTAssertParam (eventDispatcher );
96+ RCTAssertParam (bridge );
9697
97- if (( self = [super initWithFrame: CGRectZero]) ) {
98+ if (self = [super initWithFrame: CGRectZero]) {
9899 _contentInset = UIEdgeInsetsZero;
99- _eventDispatcher = eventDispatcher;
100+ _bridge = bridge;
101+ _eventDispatcher = bridge.eventDispatcher ;
100102 _placeholderTextColor = [self defaultPlaceholderTextColor ];
101103 _blurOnSubmit = NO ;
102104
103- _textView = [[RCTUITextView alloc ] initWithFrame: CGRectZero];
105+ _textView = [[RCTUITextView alloc ] initWithFrame: self .bounds];
106+ _textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
104107 _textView.backgroundColor = [UIColor clearColor ];
105108 _textView.textColor = [UIColor blackColor ];
109+ // This line actually removes 5pt (default value) left and right padding in UITextView.
110+ _textView.textContainer .lineFragmentPadding = 0 ;
106111#if !TARGET_OS_TV
107112 _textView.scrollsToTop = NO ;
108113#endif
@@ -132,7 +137,7 @@ - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
132137 // If this <TextInput> is in rich text editing mode, and the child <Text> node providing rich text
133138 // styling has a backgroundColor, then the attributedText produced by the child <Text> node will have an
134139 // NSBackgroundColor attribute. We need to forward this attribute to the text view manually because the text view
135- // always has a clear background color in -initWithEventDispatcher: .
140+ // always has a clear background color in `initWithBridge:` .
136141 //
137142 // TODO: This should be removed when the related hack in -performPendingTextUpdate is removed.
138143 if (subview.backgroundColor ) {
@@ -237,60 +242,20 @@ - (void)performPendingTextUpdate
237242 [_textView layoutIfNeeded ];
238243
239244 [self updatePlaceholderVisibility ];
240- [self updateContentSize ];
245+ [self invalidateContentSize ];
241246
242247 _blockTextShouldChange = NO ;
243248}
244249
245- - (void )updateFrames
246- {
247- // Adjust the insets so that they are as close as possible to single-line
248- // RCTTextField defaults, using the system defaults of font size 17 and a
249- // height of 31 points.
250- //
251- // We apply the left inset to the frame since a negative left text-container
252- // inset mysteriously causes the text to be hidden until the text view is
253- // first focused.
254- UIEdgeInsets adjustedFrameInset = UIEdgeInsetsZero;
255- adjustedFrameInset.left = _contentInset.left - 5 ;
256-
257- UIEdgeInsets adjustedTextContainerInset = _contentInset;
258- adjustedTextContainerInset.top += 5 ;
259- adjustedTextContainerInset.left = 0 ;
260-
261- CGRect frame = UIEdgeInsetsInsetRect (self.bounds , adjustedFrameInset);
262- _textView.frame = frame;
263- _placeholderView.frame = frame;
264- [self updateContentSize ];
265-
266- _textView.textContainerInset = adjustedTextContainerInset;
267- _placeholderView.textContainerInset = adjustedTextContainerInset;
268- }
269-
270- - (void )updateContentSize
271- {
272- CGSize size = _textView.frame .size ;
273- size.height = [_textView sizeThatFits: CGSizeMake (size.width, INFINITY)].height ;
274-
275- if (_viewDidCompleteInitialLayout && _onContentSizeChange && !CGSizeEqualToSize (_previousContentSize, size)) {
276- _previousContentSize = size;
277- _onContentSizeChange (@{
278- @" contentSize" : @{
279- @" height" : @(size.height ),
280- @" width" : @(size.width ),
281- },
282- @" target" : self.reactTag ,
283- });
284- }
285- }
286-
287250- (void )updatePlaceholder
288251{
289252 [_placeholderView removeFromSuperview ];
290253 _placeholderView = nil ;
291254
292255 if (_placeholder) {
293- _placeholderView = [[UITextView alloc ] initWithFrame: self .bounds];
256+ _placeholderView = [[UITextView alloc ] initWithFrame: _textView.frame];
257+ _placeholderView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
258+ _placeholderView.textContainer .lineFragmentPadding = 0 ;
294259 _placeholderView.userInteractionEnabled = NO ;
295260 _placeholderView.backgroundColor = [UIColor clearColor ];
296261 _placeholderView.scrollEnabled = NO ;
@@ -340,7 +305,9 @@ - (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor
340305- (void )setContentInset : (UIEdgeInsets)contentInset
341306{
342307 _contentInset = contentInset;
343- [self updateFrames ];
308+ _textView.textContainerInset = contentInset;
309+ _placeholderView.textContainerInset = contentInset;
310+ [self setNeedsLayout ];
344311}
345312
346313#pragma mark - UITextViewDelegate
@@ -503,8 +470,7 @@ - (void)setText:(NSString *)text
503470 }
504471
505472 [self updatePlaceholderVisibility ];
506- [self updateContentSize ]; // keep the text wrapping when the length of
507- // the textline has been extended longer than the length of textinputView
473+ [self invalidateContentSize ];
508474 } else if (eventLag > RCTTextUpdateLagWarningThreshold) {
509475 RCTLogWarn (@" Native TextInput(%@ ) is %zd events ahead of JS - try to make your JS faster." , self.text , eventLag);
510476 }
@@ -595,7 +561,7 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange,
595561- (void )textViewDidChange : (UITextView *)textView
596562{
597563 [self updatePlaceholderVisibility ];
598- [self updateContentSize ];
564+ [self invalidateContentSize ];
599565
600566 // Detect when textView updates happend that didn't invoke `shouldChangeTextInRange`
601567 // (e.g. typing simplified chinese in pinyin will insert and remove spaces without
@@ -664,6 +630,8 @@ - (void)textViewDidEndEditing:(UITextView *)textView
664630 eventCount: _nativeEventCount];
665631}
666632
633+ #pragma mark - UIResponder
634+
667635- (BOOL )isFirstResponder
668636{
669637 return [_textView isFirstResponder ];
@@ -695,17 +663,63 @@ - (BOOL)resignFirstResponder
695663 return [_textView resignFirstResponder ];
696664}
697665
698- - (void )layoutSubviews
666+ #pragma mark - Content Size
667+
668+ - (CGSize)contentSize
699669{
700- [super layoutSubviews ];
670+ // Returning value does NOT include insets.
671+ CGSize contentSize = self.intrinsicContentSize ;
672+ contentSize.width -= _contentInset.left + _contentInset.right ;
673+ contentSize.height -= _contentInset.top + _contentInset.bottom ;
674+ return contentSize;
675+ }
676+
677+ - (void )invalidateContentSize
678+ {
679+ CGSize contentSize = self.contentSize ;
680+
681+ if (CGSizeEqualToSize (_previousContentSize, contentSize)) {
682+ return ;
683+ }
684+ _previousContentSize = contentSize;
685+
686+ [_bridge.uiManager setIntrinsicContentSize: contentSize forView: self ];
687+
688+ if (_onContentSizeChange) {
689+ _onContentSizeChange (@{
690+ @" contentSize" : @{
691+ @" height" : @(contentSize.height ),
692+ @" width" : @(contentSize.width ),
693+ },
694+ @" target" : self.reactTag ,
695+ });
696+ }
697+ }
698+
699+ #pragma mark - Layout
700+
701+ - (CGSize)intrinsicContentSize
702+ {
703+ // Calling `sizeThatFits:` is probably more expensive method to compute
704+ // content size compare to direct access `_textView.contentSize` property,
705+ // but seems `sizeThatFits:` returns more reliable and consistent result.
706+ // Returning value DOES include insets.
707+ return [self sizeThatFits: CGSizeMake (self .bounds.size.width, INFINITY)];
708+ }
701709
702- // Start sending content size updates only after the view has been laid out
703- // otherwise we send multiple events with bad dimensions on initial render.
704- _viewDidCompleteInitialLayout = YES ;
710+ - (CGSize)sizeThatFits : (CGSize)size
711+ {
712+ return [_textView sizeThatFits: size];
713+ }
705714
706- [self updateFrames ];
715+ - (void )layoutSubviews
716+ {
717+ [super layoutSubviews ];
718+ [self invalidateContentSize ];
707719}
708720
721+ #pragma mark - Default values
722+
709723- (UIFont *)defaultPlaceholderFont
710724{
711725 return [UIFont systemFontOfSize: 17 ];
0 commit comments