11
11
12
12
#import < React/RCTConvert.h>
13
13
#import < React/RCTEventDispatcher.h>
14
+ #import < React/RCTUIManager.h>
14
15
#import < React/RCTUtils.h>
15
16
#import < React/UIView+React.h>
16
17
@@ -69,6 +70,7 @@ - (void)setContentOffset:(CGPoint)contentOffset animated:(__unused BOOL)animated
69
70
70
71
@implementation RCTTextView
71
72
{
73
+ RCTBridge *_bridge;
72
74
RCTEventDispatcher *_eventDispatcher;
73
75
74
76
NSString *_placeholder;
@@ -87,22 +89,25 @@ @implementation RCTTextView
87
89
NSInteger _nativeEventCount;
88
90
89
91
CGSize _previousContentSize;
90
- BOOL _viewDidCompleteInitialLayout;
91
92
}
92
93
93
- - (instancetype )initWithEventDispatcher : (RCTEventDispatcher *)eventDispatcher
94
+ - (instancetype )initWithBridge : (RCTBridge *)bridge
94
95
{
95
- RCTAssertParam (eventDispatcher );
96
+ RCTAssertParam (bridge );
96
97
97
- if (( self = [super initWithFrame: CGRectZero ]) ) {
98
+ if (self = [super initWithFrame: CGRectZero ]) {
98
99
_contentInset = UIEdgeInsetsZero;
99
- _eventDispatcher = eventDispatcher;
100
+ _bridge = bridge;
101
+ _eventDispatcher = bridge.eventDispatcher ;
100
102
_placeholderTextColor = [self defaultPlaceholderTextColor ];
101
103
_blurOnSubmit = NO ;
102
104
103
- _textView = [[RCTUITextView alloc ] initWithFrame: CGRectZero ];
105
+ _textView = [[RCTUITextView alloc ] initWithFrame: self .bounds];
106
+ _textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
104
107
_textView.backgroundColor = [UIColor clearColor ];
105
108
_textView.textColor = [UIColor blackColor ];
109
+ // This line actually removes 5pt (default value) left and right padding in UITextView.
110
+ _textView.textContainer .lineFragmentPadding = 0 ;
106
111
#if !TARGET_OS_TV
107
112
_textView.scrollsToTop = NO ;
108
113
#endif
@@ -132,7 +137,7 @@ - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
132
137
// If this <TextInput> is in rich text editing mode, and the child <Text> node providing rich text
133
138
// styling has a backgroundColor, then the attributedText produced by the child <Text> node will have an
134
139
// 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:` .
136
141
//
137
142
// TODO: This should be removed when the related hack in -performPendingTextUpdate is removed.
138
143
if (subview.backgroundColor ) {
@@ -237,60 +242,20 @@ - (void)performPendingTextUpdate
237
242
[_textView layoutIfNeeded ];
238
243
239
244
[self updatePlaceholderVisibility ];
240
- [self updateContentSize ];
245
+ [self invalidateContentSize ];
241
246
242
247
_blockTextShouldChange = NO ;
243
248
}
244
249
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
-
287
250
- (void )updatePlaceholder
288
251
{
289
252
[_placeholderView removeFromSuperview ];
290
253
_placeholderView = nil ;
291
254
292
255
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 ;
294
259
_placeholderView.userInteractionEnabled = NO ;
295
260
_placeholderView.backgroundColor = [UIColor clearColor ];
296
261
_placeholderView.scrollEnabled = NO ;
@@ -340,7 +305,9 @@ - (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor
340
305
- (void )setContentInset : (UIEdgeInsets)contentInset
341
306
{
342
307
_contentInset = contentInset;
343
- [self updateFrames ];
308
+ _textView.textContainerInset = contentInset;
309
+ _placeholderView.textContainerInset = contentInset;
310
+ [self setNeedsLayout ];
344
311
}
345
312
346
313
#pragma mark - UITextViewDelegate
@@ -503,8 +470,7 @@ - (void)setText:(NSString *)text
503
470
}
504
471
505
472
[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 ];
508
474
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
509
475
RCTLogWarn (@" Native TextInput(%@ ) is %zd events ahead of JS - try to make your JS faster." , self.text , eventLag);
510
476
}
@@ -595,7 +561,7 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange,
595
561
- (void )textViewDidChange : (UITextView *)textView
596
562
{
597
563
[self updatePlaceholderVisibility ];
598
- [self updateContentSize ];
564
+ [self invalidateContentSize ];
599
565
600
566
// Detect when textView updates happend that didn't invoke `shouldChangeTextInRange`
601
567
// (e.g. typing simplified chinese in pinyin will insert and remove spaces without
@@ -664,6 +630,8 @@ - (void)textViewDidEndEditing:(UITextView *)textView
664
630
eventCount: _nativeEventCount];
665
631
}
666
632
633
+ #pragma mark - UIResponder
634
+
667
635
- (BOOL )isFirstResponder
668
636
{
669
637
return [_textView isFirstResponder ];
@@ -695,17 +663,63 @@ - (BOOL)resignFirstResponder
695
663
return [_textView resignFirstResponder ];
696
664
}
697
665
698
- - (void )layoutSubviews
666
+ #pragma mark - Content Size
667
+
668
+ - (CGSize )contentSize
699
669
{
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
+ }
701
709
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
+ }
705
714
706
- [self updateFrames ];
715
+ - (void )layoutSubviews
716
+ {
717
+ [super layoutSubviews ];
718
+ [self invalidateContentSize ];
707
719
}
708
720
721
+ #pragma mark - Default values
722
+
709
723
- (UIFont *)defaultPlaceholderFont
710
724
{
711
725
return [UIFont systemFontOfSize: 17 ];
0 commit comments