mirror of
https://github.com/status-im/react-native.git
synced 2025-01-26 17:30:25 +00:00
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:
parent
3364488af0
commit
a8c45ac1c3
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user