From a8c45ac1c38866df222434b2d99d2a18b908c45f Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Tue, 27 Jun 2017 16:05:08 -0700 Subject: [PATCH] RCTTextInput: Common layout logic was moved to base class Summary: Nothing really changed except that there is no code duplication in this part anymore. More unification is coming! Reviewed By: mmmulani Differential Revision: D5144435 fbshipit-source-id: 390f795be3228907b254f8656783232013c36abe --- .../Text/RCTBackedTextInputViewProtocol.h | 1 + Libraries/Text/RCTTextField.h | 2 - Libraries/Text/RCTTextField.m | 75 --------------- Libraries/Text/RCTTextInput.h | 8 ++ Libraries/Text/RCTTextInput.m | 94 ++++++++++++++++++- Libraries/Text/RCTTextView.h | 4 - Libraries/Text/RCTTextView.m | 87 ----------------- Libraries/Text/RCTUITextView.h | 2 + Libraries/Text/RCTUITextView.m | 32 +++++-- 9 files changed, 128 insertions(+), 177 deletions(-) diff --git a/Libraries/Text/RCTBackedTextInputViewProtocol.h b/Libraries/Text/RCTBackedTextInputViewProtocol.h index 074686120..a49f1e6c9 100644 --- a/Libraries/Text/RCTBackedTextInputViewProtocol.h +++ b/Libraries/Text/RCTBackedTextInputViewProtocol.h @@ -17,5 +17,6 @@ @property (nonatomic, strong, nullable) UIColor *placeholderColor; @property (nonatomic, assign, readonly) BOOL textWasPasted; @property (nonatomic, strong, nullable) UIFont *font; +@property (nonatomic, assign) UIEdgeInsets textContainerInset; @end diff --git a/Libraries/Text/RCTTextField.h b/Libraries/Text/RCTTextField.h index 6430101da..7d50a9967 100644 --- a/Libraries/Text/RCTTextField.h +++ b/Libraries/Text/RCTTextField.h @@ -22,8 +22,6 @@ @property (nonatomic, assign) BOOL blurOnSubmit; @property (nonatomic, assign) NSInteger mostRecentEventCount; @property (nonatomic, strong) NSNumber *maxLength; -@property (nonatomic, assign) UIEdgeInsets reactPaddingInsets; -@property (nonatomic, assign) UIEdgeInsets reactBorderInsets; @property (nonatomic, copy) RCTDirectEventBlock onSelectionChange; diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m index b6fd66b87..8c85713bf 100644 --- a/Libraries/Text/RCTTextField.m +++ b/Libraries/Text/RCTTextField.m @@ -87,22 +87,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) #pragma mark - Properties -- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets -{ - _reactPaddingInsets = reactPaddingInsets; - // We apply `paddingInsets` as `_textField`'s `textContainerInset`. - _textField.textContainerInset = reactPaddingInsets; - [self setNeedsLayout]; -} - -- (void)setReactBorderInsets:(UIEdgeInsets)reactBorderInsets -{ - _reactBorderInsets = reactBorderInsets; - // We apply `borderInsets` as `_textView` layout offset. - _textField.frame = UIEdgeInsetsInsetRect(self.bounds, reactBorderInsets); - [self setNeedsLayout]; -} - - (void)setSelection:(RCTTextSelection *)selection { if (!selection) { @@ -240,65 +224,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) } } -#pragma mark - Content Size (in Yoga terms, without any insets) - -- (CGSize)contentSize -{ - // Returning value does NOT include border and padding insets. - CGSize contentSize = self.intrinsicContentSize; - UIEdgeInsets compoundInsets = self.reactCompoundInsets; - contentSize.width -= compoundInsets.left + compoundInsets.right; - contentSize.height -= compoundInsets.top + compoundInsets.bottom; - return contentSize; -} - -- (void)invalidateContentSize -{ - CGSize contentSize = self.contentSize; - - if (CGSizeEqualToSize(_previousContentSize, contentSize)) { - return; - } - _previousContentSize = contentSize; - - [_bridge.uiManager setIntrinsicContentSize:contentSize forView:self]; -} - -#pragma mark - Layout (in UIKit terms, with all insets) - -- (CGSize)intrinsicContentSize -{ - // Returning value DOES include border and padding insets. - CGSize size = _textField.intrinsicContentSize; - size.width += _reactBorderInsets.left + _reactBorderInsets.right; - size.height += _reactBorderInsets.top + _reactBorderInsets.bottom; - return size; -} - -- (CGSize)sizeThatFits:(CGSize)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 = [_textField sizeThatFits:size]; - - fittingSize.width += compoundHorizontalBorderInset; - fittingSize.height += compoundVerticalBorderInset; - - return fittingSize; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [self invalidateContentSize]; -} - #pragma mark - UITextFieldDelegate - (BOOL)textField:(RCTTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string diff --git a/Libraries/Text/RCTTextInput.h b/Libraries/Text/RCTTextInput.h index 50c59f61c..ba88d291c 100644 --- a/Libraries/Text/RCTTextInput.h +++ b/Libraries/Text/RCTTextInput.h @@ -29,4 +29,12 @@ @property (nonatomic, readonly) UIView *backedTextInputView; +@property (nonatomic, assign) UIEdgeInsets reactPaddingInsets; +@property (nonatomic, assign) UIEdgeInsets reactBorderInsets; +@property (nonatomic, assign, readonly) CGSize contentSize; + +@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange; + +- (void)invalidateContentSize; + @end diff --git a/Libraries/Text/RCTTextInput.m b/Libraries/Text/RCTTextInput.m index 8d8e1d706..21f8b8cff 100644 --- a/Libraries/Text/RCTTextInput.m +++ b/Libraries/Text/RCTTextInput.m @@ -13,9 +13,12 @@ #import #import #import +#import #import -@implementation RCTTextInput +@implementation RCTTextInput { + CGSize _previousContentSize; +} - (instancetype)initWithBridge:(RCTBridge *)bridge { @@ -39,6 +42,95 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) return nil; } +#pragma mark - Properties + +- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets +{ + _reactPaddingInsets = reactPaddingInsets; + // We apply `paddingInsets` as `backedTextInputView`'s `textContainerInset`. + self.backedTextInputView.textContainerInset = reactPaddingInsets; + [self setNeedsLayout]; +} + +- (void)setReactBorderInsets:(UIEdgeInsets)reactBorderInsets +{ + _reactBorderInsets = reactBorderInsets; + // We apply `borderInsets` as `backedTextInputView` layout offset. + self.backedTextInputView.frame = UIEdgeInsetsInsetRect(self.bounds, reactBorderInsets); + [self setNeedsLayout]; +} + +#pragma mark - Content Size (in Yoga terms, without any insets) + +- (CGSize)contentSize +{ + CGSize contentSize = self.intrinsicContentSize; + UIEdgeInsets compoundInsets = self.reactCompoundInsets; + contentSize.width -= compoundInsets.left + compoundInsets.right; + contentSize.height -= compoundInsets.top + compoundInsets.bottom; + // Returning value does NOT include border and padding insets. + return contentSize; +} + +- (void)invalidateContentSize +{ + // Updates `contentSize` property and notifies Yoga about the change, if necessary. + CGSize contentSize = self.contentSize; + + if (CGSizeEqualToSize(_previousContentSize, contentSize)) { + return; + } + _previousContentSize = contentSize; + + [_bridge.uiManager setIntrinsicContentSize:contentSize forView:self]; + + if (_onContentSizeChange) { + _onContentSizeChange(@{ + @"contentSize": @{ + @"height": @(contentSize.height), + @"width": @(contentSize.width), + }, + @"target": self.reactTag, + }); + } +} + +#pragma mark - Layout (in UIKit terms, with all insets) + +- (CGSize)intrinsicContentSize +{ + CGSize size = self.backedTextInputView.intrinsicContentSize; + size.width += _reactBorderInsets.left + _reactBorderInsets.right; + size.height += _reactBorderInsets.top + _reactBorderInsets.bottom; + // Returning value DOES include border and padding insets. + return size; +} + +- (CGSize)sizeThatFits:(CGSize)size +{ + CGFloat compoundHorizontalBorderInset = _reactBorderInsets.left + _reactBorderInsets.right; + CGFloat compoundVerticalBorderInset = _reactBorderInsets.top + _reactBorderInsets.bottom; + + size.width -= compoundHorizontalBorderInset; + size.height -= compoundVerticalBorderInset; + + // Note: `paddingInsets` was already included in `backedTextInputView` size + // because it was applied as `textContainerInset`. + CGSize fittingSize = [self.backedTextInputView sizeThatFits:size]; + + fittingSize.width += compoundHorizontalBorderInset; + fittingSize.height += compoundVerticalBorderInset; + + // Returning value DOES include border and padding insets. + return fittingSize; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + [self invalidateContentSize]; +} + #pragma mark - Accessibility - (UIView *)reactAccessibleView diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index eb0a97131..54f4e0329 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -29,12 +29,8 @@ @property (nonatomic, strong) UIFont *font; @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; @property (nonatomic, copy) RCTDirectEventBlock onSelectionChange; @property (nonatomic, copy) RCTDirectEventBlock onTextInput; @property (nonatomic, copy) RCTDirectEventBlock onScroll; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 286c40964..70381b44b 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -32,8 +32,6 @@ BOOL _blockTextShouldChange; BOOL _nativeUpdatesInFlight; NSInteger _nativeEventCount; - - CGSize _previousContentSize; } - (instancetype)initWithBridge:(RCTBridge *)bridge @@ -205,22 +203,6 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string) [self setNeedsLayout]; } -- (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets -{ - _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]; -} - - (void)setSelection:(RCTTextSelection *)selection { if (!selection) { @@ -509,75 +491,6 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, eventCount:_nativeEventCount]; } -#pragma mark - Content Size (in Yoga terms, without any insets) - -- (CGSize)contentSize -{ - // Returning value does NOT include border and padding insets. - CGSize contentSize = self.intrinsicContentSize; - UIEdgeInsets compoundInsets = self.reactCompoundInsets; - contentSize.width -= compoundInsets.left + compoundInsets.right; - contentSize.height -= compoundInsets.top + compoundInsets.bottom; - return contentSize; -} - -- (void)invalidateContentSize -{ - CGSize contentSize = self.contentSize; - - if (CGSizeEqualToSize(_previousContentSize, contentSize)) { - return; - } - _previousContentSize = contentSize; - - [_bridge.uiManager setIntrinsicContentSize:contentSize forView:self]; - - if (_onContentSizeChange) { - _onContentSizeChange(@{ - @"contentSize": @{ - @"height": @(contentSize.height), - @"width": @(contentSize.width), - }, - @"target": self.reactTag, - }); - } -} - -#pragma mark - Layout (in UIKit terms, with all insets) - -- (CGSize)intrinsicContentSize -{ - // 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 border and padding insets. - return [self sizeThatFits:CGSizeMake(self.bounds.size.width, INFINITY)]; -} - -- (CGSize)sizeThatFits:(CGSize)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 -{ - [super layoutSubviews]; - [self invalidateContentSize]; -} - #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView diff --git a/Libraries/Text/RCTUITextView.h b/Libraries/Text/RCTUITextView.h index dc15a8ef3..c48b9bd43 100644 --- a/Libraries/Text/RCTUITextView.h +++ b/Libraries/Text/RCTUITextView.h @@ -25,6 +25,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy, nullable) NSString *placeholder; @property (nonatomic, strong, nullable) UIColor *placeholderColor; +@property (nonatomic, assign) CGFloat preferredMaxLayoutWidth; + @end NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/RCTUITextView.m b/Libraries/Text/RCTUITextView.m index f82c85e97..a84680b2c 100644 --- a/Libraries/Text/RCTUITextView.m +++ b/Libraries/Text/RCTUITextView.m @@ -132,6 +132,23 @@ static UIColor *defaultPlaceholderColor() #pragma mark - Layout +- (CGFloat)preferredMaxLayoutWidth +{ + return _preferredMaxLayoutWidth ?: self.placeholderSize.width; +} + +- (CGSize)placeholderSize +{ + UIEdgeInsets textContainerInset = self.textContainerInset; + NSString *placeholder = self.placeholder ?: @""; + CGSize placeholderSize = [placeholder sizeWithAttributes:@{NSFontAttributeName: self.font ?: defaultPlaceholderFont()}]; + placeholderSize = CGSizeMake(RCTCeilPixelValue(placeholderSize.width), RCTCeilPixelValue(placeholderSize.height)); + placeholderSize.width += textContainerInset.left + textContainerInset.right; + placeholderSize.height += textContainerInset.top + textContainerInset.bottom; + // Returning size DOES contain `textContainerInset` (aka `padding`; as `sizeThatFits:` does). + return placeholderSize; +} + - (void)layoutSubviews { [super layoutSubviews]; @@ -142,18 +159,17 @@ static UIColor *defaultPlaceholderColor() _placeholderView.frame = textFrame; } +- (CGSize)intrinsicContentSize +{ + return [self sizeThatFits:CGSizeMake(self.preferredMaxLayoutWidth, INFINITY)]; +} + - (CGSize)sizeThatFits:(CGSize)size { // Returned fitting size depends on text size and placeholder size. CGSize textSize = [self fixedSizeThatFits:size]; - - UIEdgeInsets padddingInsets = self.textContainerInset; - NSString *placeholder = self.placeholder ?: @""; - CGSize placeholderSize = [placeholder 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; - + CGSize placeholderSize = self.placeholderSize; + // Returning size DOES contain `textContainerInset`. return CGSizeMake(MAX(textSize.width, placeholderSize.width), MAX(textSize.height, placeholderSize.height)); }