Multiline <TextInput> 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
This commit is contained in:
Valentin Shergin 2017-05-29 15:56:46 -07:00 коммит произвёл Facebook Github Bot
Родитель 4e40521620
Коммит 48650226e8
5 изменённых файлов: 88 добавлений и 24 удалений

Просмотреть файл

@ -21,7 +21,6 @@
@property (nonatomic, assign) BOOL blurOnSubmit; @property (nonatomic, assign) BOOL blurOnSubmit;
@property (nonatomic, assign) BOOL clearTextOnFocus; @property (nonatomic, assign) BOOL clearTextOnFocus;
@property (nonatomic, assign) BOOL selectTextOnFocus; @property (nonatomic, assign) BOOL selectTextOnFocus;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, copy) NSString *text; @property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) UIColor *placeholderTextColor; @property (nonatomic, strong) UIColor *placeholderTextColor;
@ -30,6 +29,8 @@
@property (nonatomic, assign) NSInteger mostRecentEventCount; @property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, strong) NSNumber *maxLength; @property (nonatomic, strong) NSNumber *maxLength;
@property (nonatomic, assign, readonly) CGSize contentSize; @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 onChange;
@property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange; @property (nonatomic, copy) RCTDirectEventBlock onContentSizeChange;

Просмотреть файл

@ -44,7 +44,6 @@
RCTAssertParam(bridge); RCTAssertParam(bridge);
if (self = [super initWithFrame:CGRectZero]) { if (self = [super initWithFrame:CGRectZero]) {
_contentInset = UIEdgeInsetsZero;
_bridge = bridge; _bridge = bridge;
_eventDispatcher = bridge.eventDispatcher; _eventDispatcher = bridge.eventDispatcher;
_blurOnSubmit = NO; _blurOnSubmit = NO;
@ -203,12 +202,22 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
- (void)setFont:(UIFont *)font - (void)setFont:(UIFont *)font
{ {
_textView.font = font; _textView.font = font;
[self setNeedsLayout];
} }
- (void)setContentInset:(UIEdgeInsets)contentInset - (void)setReactPaddingInsets:(UIEdgeInsets)reactPaddingInsets
{ {
_contentInset = contentInset; _reactPaddingInsets = reactPaddingInsets;
_textView.textContainerInset = contentInset; // 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]; [self setNeedsLayout];
} }
@ -270,6 +279,7 @@ static NSAttributedString *removeReactTagFromString(NSAttributedString *string)
- (void)setPlaceholder:(NSString *)placeholder - (void)setPlaceholder:(NSString *)placeholder
{ {
_textView.placeholderText = placeholder; _textView.placeholderText = placeholder;
[self setNeedsLayout];
} }
- (UIColor *)placeholderTextColor - (UIColor *)placeholderTextColor
@ -541,10 +551,11 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange,
- (CGSize)contentSize - (CGSize)contentSize
{ {
// Returning value does NOT include insets. // Returning value does NOT include border and padding insets.
CGSize contentSize = self.intrinsicContentSize; CGSize contentSize = self.intrinsicContentSize;
contentSize.width -= _contentInset.left + _contentInset.right; UIEdgeInsets compoundInsets = self.reactCompoundInsets;
contentSize.height -= _contentInset.top + _contentInset.bottom; contentSize.width -= compoundInsets.left + compoundInsets.right;
contentSize.height -= compoundInsets.top + compoundInsets.bottom;
return contentSize; return contentSize;
} }
@ -577,13 +588,26 @@ static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange,
// Calling `sizeThatFits:` is probably more expensive method to compute // Calling `sizeThatFits:` is probably more expensive method to compute
// content size compare to direct access `_textView.contentSize` property, // content size compare to direct access `_textView.contentSize` property,
// but seems `sizeThatFits:` returns more reliable and consistent result. // 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)]; return [self sizeThatFits:CGSizeMake(self.bounds.size.width, INFINITY)];
} }
- (CGSize)sizeThatFits:(CGSize)size - (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 - (void)layoutSubviews

Просмотреть файл

@ -83,9 +83,11 @@ RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, textView.dataDetectorTypes, UIDataDet
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
{ {
NSNumber *reactTag = shadowView.reactTag; NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets; UIEdgeInsets borderAsInsets = shadowView.borderAsInsets;
UIEdgeInsets paddingAsInsets = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTTextView *> *viewRegistry) { return ^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTTextView *> *viewRegistry) {
viewRegistry[reactTag].contentInset = padding; viewRegistry[reactTag].reactBorderInsets = borderAsInsets;
viewRegistry[reactTag].reactPaddingInsets = paddingAsInsets;
}; };
} }

Просмотреть файл

@ -10,6 +10,7 @@
#import "RCTUITextView.h" #import "RCTUITextView.h"
#import <React/UIView+React.h> #import <React/UIView+React.h>
#import <React/RCTUtils.h>
@implementation RCTUITextView @implementation RCTUITextView
{ {
@ -123,6 +124,21 @@ static UIColor *defaultPlaceholderTextColor()
} }
- (CGSize)sizeThatFits:(CGSize)size - (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 // 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. // when calling `sizeThatFits:`. Use a copy so that self is not screwed up.

Просмотреть файл

@ -763,18 +763,39 @@ exports.examples = [
title: 'TextInput Intrinsic Size', title: 'TextInput Intrinsic Size',
render: function() { render: function() {
return ( return (
<View style={{height: 80}}> <View>
<TextInput <Text>Singleline TextInput</Text>
style={{ <View style={{height: 80}}>
position: 'absolute', <TextInput
fontSize: 16, style={{
backgroundColor: '#eeeeee', position: 'absolute',
borderWidth: 5, fontSize: 16,
padding: 10, backgroundColor: '#eeeeee',
paddingTop: 20, borderColor: '#666666',
}} borderWidth: 5,
placeholder="Placeholder defines intrinsic size" padding: 10,
/> paddingTop: 20,
}}
placeholder="Placeholder defines intrinsic size"
/>
</View>
<Text>Multiline TextInput</Text>
<View style={{height: 80}}>
<TextInput
style={{
position: 'absolute',
fontSize: 16,
backgroundColor: '#eeeeee',
borderColor: '#666666',
borderWidth: 5,
padding: 10,
paddingTop: 20,
borderTopWidth: 20,
}}
multiline={true}
placeholder="Placeholder defines intrinsic size"
/>
</View>
</View> </View>
); );
} }