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 committed by Facebook Github Bot
parent 4e40521620
commit 48650226e8
5 changed files with 88 additions and 24 deletions

View File

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

View File

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

View File

@ -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<NSNumber *, RCTTextView *> *viewRegistry) {
viewRegistry[reactTag].contentInset = padding;
viewRegistry[reactTag].reactBorderInsets = borderAsInsets;
viewRegistry[reactTag].reactPaddingInsets = paddingAsInsets;
};
}

View File

@ -10,6 +10,7 @@
#import "RCTUITextView.h"
#import <React/UIView+React.h>
#import <React/RCTUtils.h>
@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.

View File

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