From 48650226e83fcdd0040ffa4c8884afe62047d4e0 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Mon, 29 May 2017 15:56:46 -0700 Subject: [PATCH] Multiline was fixed to match layout logic of singlelined one Summary: Now padding, border and intinsic sizes are computed same way as for singlelined text input. Reviewed By: mmmulani Differential Revision: D5075880 fbshipit-source-id: 1bc2fd479c13a003c717b1fc3d9c69f4639d4444 --- Libraries/Text/RCTTextView.h | 3 +- Libraries/Text/RCTTextView.m | 42 +++++++++++++++++++++------ Libraries/Text/RCTTextViewManager.m | 6 ++-- Libraries/Text/RCTUITextView.m | 16 ++++++++++ RNTester/js/TextInputExample.ios.js | 45 +++++++++++++++++++++-------- 5 files changed, 88 insertions(+), 24 deletions(-) diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index d77ff5777..75767f501 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -21,7 +21,6 @@ @property (nonatomic, assign) BOOL blurOnSubmit; @property (nonatomic, assign) BOOL clearTextOnFocus; @property (nonatomic, assign) BOOL selectTextOnFocus; -@property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, copy) NSString *text; @property (nonatomic, strong) UIColor *placeholderTextColor; @@ -30,6 +29,8 @@ @property (nonatomic, assign) NSInteger mostRecentEventCount; @property (nonatomic, strong) NSNumber *maxLength; @property (nonatomic, assign, readonly) CGSize contentSize; +@property (nonatomic, assign) UIEdgeInsets reactPaddingInsets; +@property (nonatomic, assign) UIEdgeInsets reactBorderInsets; @property (nonatomic, copy) RCTDirectEventBlock onChange; @property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 76ebb1eca..1e609b6f3 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -44,7 +44,6 @@ RCTAssertParam(bridge); if (self = [super initWithFrame:CGRectZero]) { - _contentInset = UIEdgeInsetsZero; _bridge = bridge; _eventDispatcher = bridge.eventDispatcher; _blurOnSubmit = NO; @@ -203,12 +202,22 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string) - (void)setFont:(UIFont *)font { _textView.font = font; + [self setNeedsLayout]; } -- (void)setContentInset:(UIEdgeInsets)contentInset +- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets { - _contentInset = contentInset; - _textView.textContainerInset = contentInset; + _reactPaddingInsets = reactPaddingInsets; + // We apply `paddingInsets` as `_textView`'s `textContainerInset`. + _textView.textContainerInset = reactPaddingInsets; + [self setNeedsLayout]; +} + +- (void)setReactBorderInsets:(UIEdgeInsets)reactBorderInsets +{ + _reactBorderInsets = reactBorderInsets; + // We apply `borderInsets` as `_textView` layout offset. + _textView.frame = UIEdgeInsetsInsetRect(self.bounds, reactBorderInsets); [self setNeedsLayout]; } @@ -270,6 +279,7 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string) - (void)setPlaceholder:(NSString *)placeholder { _textView.placeholderText = placeholder; + [self setNeedsLayout]; } - (UIColor *)placeholderTextColor @@ -541,10 +551,11 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, - (CGSize)contentSize { - // Returning value does NOT include insets. + // Returning value does NOT include border and padding insets. CGSize contentSize = self.intrinsicContentSize; - contentSize.width -= _contentInset.left + _contentInset.right; - contentSize.height -= _contentInset.top + _contentInset.bottom; + UIEdgeInsets compoundInsets = self.reactCompoundInsets; + contentSize.width -= compoundInsets.left + compoundInsets.right; + contentSize.height -= compoundInsets.top + compoundInsets.bottom; return contentSize; } @@ -577,13 +588,26 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, // Calling `sizeThatFits:` is probably more expensive method to compute // content size compare to direct access `_textView.contentSize` property, // but seems `sizeThatFits:` returns more reliable and consistent result. - // Returning value DOES include insets. + // Returning value DOES include border and padding insets. return [self sizeThatFits:CGSizeMake(self.bounds.size.width, INFINITY)]; } - (CGSize)sizeThatFits:(CGSize)size { - return [_textView sizeThatFits:size]; + CGFloat compoundHorizontalBorderInset = _reactBorderInsets.left + _reactBorderInsets.right; + CGFloat compoundVerticalBorderInset = _reactBorderInsets.top + _reactBorderInsets.bottom; + + size.width -= compoundHorizontalBorderInset; + size.height -= compoundVerticalBorderInset; + + // Note: `paddingInsets` already included in `_textView` size + // because it was applied as `textContainerInset`. + CGSize fittingSize = [_textView sizeThatFits:size]; + + fittingSize.width += compoundHorizontalBorderInset; + fittingSize.height += compoundVerticalBorderInset; + + return fittingSize; } - (void)layoutSubviews diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index b32eeaf5b..5dcfc1539 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -83,9 +83,11 @@ RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, textView.dataDetectorTypes, UIDataDet - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView { NSNumber *reactTag = shadowView.reactTag; - UIEdgeInsets padding = shadowView.paddingAsInsets; + UIEdgeInsets borderAsInsets = shadowView.borderAsInsets; + UIEdgeInsets paddingAsInsets = shadowView.paddingAsInsets; return ^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - viewRegistry[reactTag].contentInset = padding; + viewRegistry[reactTag].reactBorderInsets = borderAsInsets; + viewRegistry[reactTag].reactPaddingInsets = paddingAsInsets; }; } diff --git a/Libraries/Text/RCTUITextView.m b/Libraries/Text/RCTUITextView.m index 2498f6912..b5ed21688 100644 --- a/Libraries/Text/RCTUITextView.m +++ b/Libraries/Text/RCTUITextView.m @@ -10,6 +10,7 @@ #import "RCTUITextView.h" #import +#import @implementation RCTUITextView { @@ -123,6 +124,21 @@ static UIColor *defaultPlaceholderTextColor() } - (CGSize)sizeThatFits:(CGSize)size +{ + // Returned fitting size depends on text size and placeholder size. + CGSize textSize = [self fixedSizeThatFits:size]; + + UIEdgeInsets padddingInsets = self.textContainerInset; + NSString *placeholderText = self.placeholderText ?: @""; + CGSize placeholderSize = [placeholderText sizeWithAttributes:@{NSFontAttributeName: self.font ?: defaultPlaceholderFont()}]; + placeholderSize = CGSizeMake(RCTCeilPixelValue(placeholderSize.width), RCTCeilPixelValue(placeholderSize.height)); + placeholderSize.width += padddingInsets.left + padddingInsets.right; + placeholderSize.height += padddingInsets.top + padddingInsets.bottom; + + return CGSizeMake(MAX(textSize.width, placeholderSize.width), MAX(textSize.height, placeholderSize.height)); +} + +- (CGSize)fixedSizeThatFits:(CGSize)size { // UITextView on iOS 8 has a bug that automatically scrolls to the top // when calling `sizeThatFits:`. Use a copy so that self is not screwed up. diff --git a/RNTester/js/TextInputExample.ios.js b/RNTester/js/TextInputExample.ios.js index 10b537681..824c48440 100644 --- a/RNTester/js/TextInputExample.ios.js +++ b/RNTester/js/TextInputExample.ios.js @@ -763,18 +763,39 @@ exports.examples = [ title: 'TextInput Intrinsic Size', render: function() { return ( - - + + Singleline TextInput + + + + Multiline TextInput + + + ); }