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:
parent
4e40521620
commit
48650226e8
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue