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
This commit is contained in:
Valentin Shergin 2017-06-27 16:05:08 -07:00 committed by Facebook Github Bot
parent 3364488af0
commit a8c45ac1c3
9 changed files with 128 additions and 177 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -29,4 +29,12 @@
@property (nonatomic, readonly) UIView<RCTBackedTextInputViewProtocol> *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

View File

@ -13,9 +13,12 @@
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTUtils.h>
#import <React/RCTUIManager.h>
#import <React/UIView+React.h>
@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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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));
}