From 3d6f44221bc68ef2bf566bf024d6d2dd9ebc559c Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Mon, 28 Sep 2015 11:03:13 -0400 Subject: [PATCH 1/9] [TextInput] Add auto-grow support for multiline TextInput components. --- Examples/UIExplorer/TextInputExample.ios.js | 16 +++++++++ Libraries/Components/TextInput/TextInput.js | 6 ++++ Libraries/Text/RCTTextView.h | 4 ++- Libraries/Text/RCTTextView.m | 38 ++++++++++++++++++--- Libraries/Text/RCTTextViewManager.m | 3 +- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index d51a95e33bb5fd..c4f0bdafd2b29c 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -149,6 +149,15 @@ var styles = StyleSheet.create({ right: 5, backgroundColor: 'red', }, + multilineAutoGrow: { + borderWidth: 0.5, + borderColor: '#0f0f0f', + flex: 1, + fontSize: 13, + height: 30, + padding: 4, + marginBottom: 4, + }, eventLabel: { margin: 3, fontSize: 12, @@ -434,6 +443,13 @@ exports.examples = [ style={styles.multiline}> + + ); } diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index f96c1852fca60a..965010eb0e356b 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -34,6 +34,7 @@ var onlyMultiline = { onSelectionChange: true, // not supported in Open Source yet onTextInput: true, // not supported in Open Source yet children: true, + autoGrow: true, }; var notMultiline = { @@ -100,6 +101,11 @@ var TextInput = React.createClass({ * If false, disables auto-correct. The default value is true. */ autoCorrect: PropTypes.bool, + /** + * If true, and the input is multiline, the input's height will grow automatically. The default value is true. + * @platorm ios + */ + autoGrow: PropTypes.bool, /** * If true, focuses the input on componentDidMount. * The default value is false. diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index c5012ec0917411..fe61b145d22536 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -10,6 +10,7 @@ #import #import "RCTView.h" +#import "RCTUIManager.h" #import "UIView+React.h" @class RCTEventDispatcher; @@ -17,6 +18,7 @@ @interface RCTTextView : RCTView @property (nonatomic, assign) BOOL autoCorrect; +@property (nonatomic, assign) BOOL autoGrow; @property (nonatomic, assign) BOOL clearTextOnFocus; @property (nonatomic, assign) BOOL selectTextOnFocus; @property (nonatomic, assign) UIEdgeInsets contentInset; @@ -28,6 +30,6 @@ @property (nonatomic, assign) NSInteger mostRecentEventCount; @property (nonatomic, strong) NSNumber *maxLength; -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; @end diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index d21360ed26c1be..c7182ae491824a 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -12,30 +12,35 @@ #import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "RCTUtils.h" +#import "RCTUIManager.h" #import "UIView+React.h" @implementation RCTTextView { + RCTBridge *_bridge; RCTEventDispatcher *_eventDispatcher; BOOL _jsRequestingFirstResponder; + BOOL _autoGrow; + float _origHeight; NSString *_placeholder; UITextView *_placeholderView; UITextView *_textView; NSInteger _nativeEventCount; } -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +- (instancetype)initWithBridge:(RCTBridge *)bridge { - RCTAssertParam(eventDispatcher); + RCTAssertParam(bridge); if ((self = [super initWithFrame:CGRectZero])) { + _bridge = bridge; + _autoGrow = false; _contentInset = UIEdgeInsetsZero; - _eventDispatcher = eventDispatcher; + _eventDispatcher = _bridge.eventDispatcher; _placeholderTextColor = [self defaultPlaceholderTextColor]; _textView = [[UITextView alloc] initWithFrame:self.bounds]; _textView.backgroundColor = [UIColor clearColor]; - _textView.scrollsToTop = NO; _textView.delegate = self; [self addSubview:_textView]; } @@ -67,6 +72,10 @@ - (void)updateFrames _textView.textContainerInset = adjustedTextContainerInset; _placeholderView.textContainerInset = adjustedTextContainerInset; + + if (! _origHeight) { + _origHeight = self.frame.size.height; + } } - (void)updatePlaceholder @@ -194,6 +203,11 @@ - (BOOL)autoCorrect return _textView.autocorrectionType == UITextAutocorrectionTypeYes; } +- (void)setAutoGrow:(BOOL)autoGrow +{ + _autoGrow = autoGrow; +} + - (BOOL)textViewShouldBeginEditing:(UITextView *)textView { if (_selectTextOnFocus) { @@ -219,6 +233,22 @@ - (void)textViewDidBeginEditing:(UITextView *)textView - (void)textViewDidChange:(UITextView *)textView { + if (_autoGrow) { + _textView.scrollEnabled = NO; + + [_textView sizeToFit]; + float newHeight; + + if (_textView.frame.size.height >= _origHeight) { + newHeight = _textView.frame.size.height; + } else { + newHeight = _origHeight; + } + + CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, newHeight); + [_bridge.uiManager setFrame:newFrame forView:self]; + } + [self _setPlaceholderVisibility]; _nativeEventCount++; [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index f47a106bdf49a9..a2ba4491f9d668 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -21,10 +21,11 @@ @implementation RCTTextViewManager - (UIView *)view { - return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; + return [[RCTTextView alloc] initWithBridge:self.bridge]; } RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) +RCT_EXPORT_VIEW_PROPERTY(autoGrow, BOOL) RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) From e5c0e13d9b4bda7bf8737abf6155a2d77c0798c8 Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Mon, 28 Sep 2015 11:14:04 -0400 Subject: [PATCH 2/9] =?UTF-8?q?Fix=20the=20docs=20fro=20`autoGrow`?= =?UTF-8?q?=E2=80=A6it=20defaults=20to=20false,=20not=20true.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Libraries/Components/TextInput/TextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 965010eb0e356b..65cc996e1607ce 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -102,7 +102,7 @@ var TextInput = React.createClass({ */ autoCorrect: PropTypes.bool, /** - * If true, and the input is multiline, the input's height will grow automatically. The default value is true. + * If true, and the input is multiline, the input's height will grow automatically. The default value is false. * @platorm ios */ autoGrow: PropTypes.bool, From 3645a6e12a16a4daf24eb8201b58bfaa9ec0daa2 Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Mon, 28 Sep 2015 11:17:33 -0400 Subject: [PATCH 3/9] scrollsToTop should be NO on the UITextView --- Libraries/Text/RCTTextView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index c7182ae491824a..1004023a5853fb 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -41,6 +41,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge _textView = [[UITextView alloc] initWithFrame:self.bounds]; _textView.backgroundColor = [UIColor clearColor]; + _textView.scrollsToTop = NO; _textView.delegate = self; [self addSubview:_textView]; } From 88cb14fd5fa94f76c27b5e77dd1d802fc8d4fa5b Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Mon, 28 Sep 2015 11:27:25 -0400 Subject: [PATCH 4/9] Only update the height if it actually needs to change. --- Libraries/Text/RCTTextView.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 1004023a5853fb..e03ce82d8a5d89 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -235,10 +235,11 @@ - (void)textViewDidBeginEditing:(UITextView *)textView - (void)textViewDidChange:(UITextView *)textView { if (_autoGrow) { + float currentHeight = _textView.frame.size.height; + float newHeight; + _textView.scrollEnabled = NO; - [_textView sizeToFit]; - float newHeight; if (_textView.frame.size.height >= _origHeight) { newHeight = _textView.frame.size.height; @@ -246,8 +247,11 @@ - (void)textViewDidChange:(UITextView *)textView newHeight = _origHeight; } - CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, newHeight); - [_bridge.uiManager setFrame:newFrame forView:self]; + if (newHeight != currentHeight) { + CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, newHeight); + [_bridge.uiManager setFrame:newFrame + forView:self]; + } } [self _setPlaceholderVisibility]; From c6b23b7a9ad78f1ded8e4a50265982d33ec86043 Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Mon, 28 Sep 2015 14:14:27 -0400 Subject: [PATCH 5/9] The text view also needs to update whenever the font size/family changes, as well as the placeholder updates. Also fixes issue with multi-line placeholders. --- Libraries/Text/RCTTextView.m | 63 ++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index e03ce82d8a5d89..75fafe344592c4 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -77,6 +77,7 @@ - (void)updateFrames if (! _origHeight) { _origHeight = self.frame.size.height; } + [self updateTextViewFrame]; } - (void)updatePlaceholder @@ -99,6 +100,44 @@ - (void)updatePlaceholder } } +- (void)updateTextViewFrame +{ + if (self.superview == nil) { + return; + } + + if (_autoGrow) { + UITextView *textView; + if (_placeholderView) { + textView = [_placeholderView isHidden] ? _textView : _placeholderView; + } else { + textView = _textView; + } + + if (CGRectIsEmpty(self.frame)) { + return; + } + + float currentHeight = textView.frame.size.height; + float newHeight; + + textView.scrollEnabled = NO; + [textView sizeToFit]; + + if (textView.frame.size.height >= _origHeight) { + newHeight = textView.frame.size.height; + } else { + newHeight = _origHeight; + } + + if (newHeight != currentHeight) { + CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, newHeight); + [_bridge.uiManager setFrame:newFrame + forView:self]; + } + } +} + - (UIFont *)font { return _textView.font; @@ -108,6 +147,7 @@ - (void)setFont:(UIFont *)font { _textView.font = font; [self updatePlaceholder]; + [self updateTextViewFrame]; } - (UIColor *)textColor @@ -124,6 +164,7 @@ - (void)setPlaceholder:(NSString *)placeholder { _placeholder = placeholder; [self updatePlaceholder]; + [self updateTextViewFrame]; } - (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor @@ -234,27 +275,9 @@ - (void)textViewDidBeginEditing:(UITextView *)textView - (void)textViewDidChange:(UITextView *)textView { - if (_autoGrow) { - float currentHeight = _textView.frame.size.height; - float newHeight; - - _textView.scrollEnabled = NO; - [_textView sizeToFit]; - - if (_textView.frame.size.height >= _origHeight) { - newHeight = _textView.frame.size.height; - } else { - newHeight = _origHeight; - } - - if (newHeight != currentHeight) { - CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, newHeight); - [_bridge.uiManager setFrame:newFrame - forView:self]; - } - } - [self _setPlaceholderVisibility]; + [self updateTextViewFrame]; + _nativeEventCount++; [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange reactTag:self.reactTag From 493f413864f1160c387818bb697ecbd3b08f31e8 Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Mon, 28 Sep 2015 17:31:52 -0400 Subject: [PATCH 6/9] =?UTF-8?q?No=20longer=20resize=20the=20text=20view=20?= =?UTF-8?q?to=20fit=20the=20placeholder.=20=20It=20causes=20an=20excess=20?= =?UTF-8?q?amount=20of=20frame=20updates,=20and=20caused=20a=20=E2=80=9Cfl?= =?UTF-8?q?icker=E2=80=9D=20issue,=20which=20caused=20the=20height=20to=20?= =?UTF-8?q?jump=20occasionally.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Libraries/Text/RCTTextView.m | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 75fafe344592c4..dafe00d8d3d160 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -77,7 +77,6 @@ - (void)updateFrames if (! _origHeight) { _origHeight = self.frame.size.height; } - [self updateTextViewFrame]; } - (void)updatePlaceholder @@ -107,25 +106,18 @@ - (void)updateTextViewFrame } if (_autoGrow) { - UITextView *textView; - if (_placeholderView) { - textView = [_placeholderView isHidden] ? _textView : _placeholderView; - } else { - textView = _textView; - } - if (CGRectIsEmpty(self.frame)) { return; } - float currentHeight = textView.frame.size.height; + float currentHeight = _textView.frame.size.height; float newHeight; - textView.scrollEnabled = NO; - [textView sizeToFit]; + _textView.scrollEnabled = NO; + [_textView sizeToFit]; - if (textView.frame.size.height >= _origHeight) { - newHeight = textView.frame.size.height; + if (_textView.frame.size.height >= _origHeight) { + newHeight = _textView.frame.size.height; } else { newHeight = _origHeight; } @@ -164,7 +156,6 @@ - (void)setPlaceholder:(NSString *)placeholder { _placeholder = placeholder; [self updatePlaceholder]; - [self updateTextViewFrame]; } - (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor From d2798ddb2c14a15729681e4c646a10d610755538 Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Tue, 29 Sep 2015 15:55:48 -0400 Subject: [PATCH 7/9] Must update the height when the text is set as well. --- Libraries/Text/RCTTextView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index dafe00d8d3d160..c4e9cf0a9af487 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -211,6 +211,7 @@ - (void)setText:(NSString *)text UITextRange *selection = _textView.selectedTextRange; _textView.text = text; [self _setPlaceholderVisibility]; + [self updateTextViewFrame]; _textView.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds } else if (eventLag > RCTTextUpdateLagWarningThreshold) { RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", self.text, eventLag); From 18cb62571eb5788bfd2ba8063de79262db9dcfe3 Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Tue, 29 Sep 2015 16:14:31 -0400 Subject: [PATCH 8/9] Add the `maxHeight` property to allow you to set a max height the input can reach when using autoGrow --- Libraries/Components/TextInput/TextInput.js | 6 ++++++ Libraries/Text/RCTTextView.h | 1 + Libraries/Text/RCTTextView.m | 11 ++++++++--- Libraries/Text/RCTTextViewManager.m | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 65cc996e1607ce..b19886bf2a3fce 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -35,6 +35,7 @@ var onlyMultiline = { onTextInput: true, // not supported in Open Source yet children: true, autoGrow: true, + maxHeight: true, }; var notMultiline = { @@ -106,6 +107,11 @@ var TextInput = React.createClass({ * @platorm ios */ autoGrow: PropTypes.bool, + /** + * The maximum height the input should grow to when autoGrow is true. + * @platorm ios + */ + maxHeight: PropTypes.number, /** * If true, focuses the input on componentDidMount. * The default value is false. diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index fe61b145d22536..4df27d37bf6d58 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -19,6 +19,7 @@ @property (nonatomic, assign) BOOL autoCorrect; @property (nonatomic, assign) BOOL autoGrow; +@property (nonatomic, assign) float maxHeight; @property (nonatomic, assign) BOOL clearTextOnFocus; @property (nonatomic, assign) BOOL selectTextOnFocus; @property (nonatomic, assign) UIEdgeInsets contentInset; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index c4e9cf0a9af487..81e3b8ec97e5e5 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -22,6 +22,7 @@ @implementation RCTTextView BOOL _jsRequestingFirstResponder; BOOL _autoGrow; float _origHeight; + float _maxHeight; NSString *_placeholder; UITextView *_placeholderView; UITextView *_textView; @@ -113,7 +114,6 @@ - (void)updateTextViewFrame float currentHeight = _textView.frame.size.height; float newHeight; - _textView.scrollEnabled = NO; [_textView sizeToFit]; if (_textView.frame.size.height >= _origHeight) { @@ -121,9 +121,9 @@ - (void)updateTextViewFrame } else { newHeight = _origHeight; } - + if (newHeight != currentHeight) { - CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, newHeight); + CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, fminf(newHeight, _maxHeight)); [_bridge.uiManager setFrame:newFrame forView:self]; } @@ -242,6 +242,11 @@ - (void)setAutoGrow:(BOOL)autoGrow _autoGrow = autoGrow; } +- (void)setMaxHeight:(float)maxHeight +{ + _maxHeight = maxHeight; +} + - (BOOL)textViewShouldBeginEditing:(UITextView *)textView { if (_selectTextOnFocus) { diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index a2ba4491f9d668..7b4851ce9b6ac7 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -26,6 +26,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) RCT_EXPORT_VIEW_PROPERTY(autoGrow, BOOL) +RCT_EXPORT_VIEW_PROPERTY(maxHeight, float) RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) From 848eaef55fba1d9609bfa2c1f582e2965f8a7bfb Mon Sep 17 00:00:00 2001 From: Dan Horrigan Date: Tue, 29 Sep 2015 18:21:15 -0400 Subject: [PATCH 9/9] Fix an issue when no maxHeight is set. --- Libraries/Text/RCTTextView.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 81e3b8ec97e5e5..74435ffed9d8b1 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -122,8 +122,12 @@ - (void)updateTextViewFrame newHeight = _origHeight; } + if (_maxHeight > _origHeight) { + newHeight = fminf(newHeight, _maxHeight); + } + if (newHeight != currentHeight) { - CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, fminf(newHeight, _maxHeight)); + CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, newHeight); [_bridge.uiManager setFrame:newFrame forView:self]; }