mirror of
https://github.com/status-im/react-native.git
synced 2025-01-27 01:40:08 +00:00
Fix flaky scrolling for TextInput when using rich text
Summary: public This diff fixes the jumpy scrolling for multiline `<TextInput>` when using nested `<Text>` components to implement rich text highlighting. The fix is to disable scrolling on the underlying UITextView, and nest it inside another UIScrollView that we control. Reviewed By: ericvicenti, tadeuzagallo Differential Revision: D2674670 fb-gh-sync-id: bacee3ae485523cc26ca8102b714e081df230629
This commit is contained in:
parent
469a65217c
commit
b5be05d82b
@ -301,6 +301,50 @@ exports.displayName = (undefined: ?string);
|
||||
exports.title = '<TextInput>';
|
||||
exports.description = 'Single and multi-line text inputs.';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Multiline',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder="multiline text input"
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline text input with font styles and placeholder"
|
||||
multiline={true}
|
||||
clearTextOnFocus={true}
|
||||
autoCorrect={true}
|
||||
autoCapitalize="words"
|
||||
placeholderTextColor="red"
|
||||
keyboardType="url"
|
||||
style={[styles.multiline, styles.multilineWithFontStyles]}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="uneditable multiline text input"
|
||||
editable={false}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline with children"
|
||||
multiline={true}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
returnKeyType="go"
|
||||
style={styles.multiline}>
|
||||
<View style={styles.multilineChild}/>
|
||||
</TextInput>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Attributed text',
|
||||
render: function() {
|
||||
return <TokenizedTextExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Auto-focus',
|
||||
render: function() {
|
||||
@ -544,50 +588,6 @@ exports.examples = [
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Multiline',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder="multiline text input"
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline text input with font styles and placeholder"
|
||||
multiline={true}
|
||||
clearTextOnFocus={true}
|
||||
autoCorrect={true}
|
||||
autoCapitalize="words"
|
||||
placeholderTextColor="red"
|
||||
keyboardType="url"
|
||||
style={[styles.multiline, styles.multilineWithFontStyles]}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="uneditable multiline text input"
|
||||
editable={false}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline with children"
|
||||
multiline={true}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
returnKeyType="go"
|
||||
style={styles.multiline}>
|
||||
<View style={styles.multilineChild}/>
|
||||
</TextInput>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Attributed text',
|
||||
render: function() {
|
||||
return <TokenizedTextExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Blur on submit',
|
||||
render: function(): ReactElement { return <BlurOnSubmitExample />; },
|
||||
|
@ -22,7 +22,6 @@
|
||||
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
||||
@property (nonatomic, copy) NSString *text;
|
||||
@property (nonatomic, strong) UIColor *textColor;
|
||||
@property (nonatomic, strong) UIColor *placeholderTextColor;
|
||||
@property (nonatomic, strong) UIFont *font;
|
||||
@property (nonatomic, assign) NSInteger mostRecentEventCount;
|
||||
|
@ -44,6 +44,7 @@
|
||||
NSMutableArray<UIView *> *_subviews;
|
||||
BOOL _blockTextShouldChange;
|
||||
UITextRange *_previousSelectionRange;
|
||||
UIScrollView *_scrollView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
@ -55,15 +56,19 @@
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_placeholderTextColor = [self defaultPlaceholderTextColor];
|
||||
|
||||
_textView = [[RCTUITextView alloc] initWithFrame:self.bounds];
|
||||
_textView = [[RCTUITextView alloc] initWithFrame:CGRectZero];
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.scrollsToTop = NO;
|
||||
_textView.scrollEnabled = NO;
|
||||
_textView.delegate = self;
|
||||
|
||||
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero];
|
||||
[_scrollView addSubview:_textView];
|
||||
|
||||
_previousSelectionRange = _textView.selectedTextRange;
|
||||
|
||||
_subviews = [NSMutableArray new];
|
||||
[self addSubview:_textView];
|
||||
[self addSubview:_scrollView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -139,16 +144,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
// we temporarily block all textShouldChange events so they are not applied.
|
||||
_blockTextShouldChange = YES;
|
||||
|
||||
// We compute the new selectedRange manually to make sure the cursor is at the
|
||||
// end of the newly inserted/deleted text after update.
|
||||
NSRange range = _textView.selectedRange;
|
||||
CGPoint contentOffset = _textView.contentOffset;
|
||||
|
||||
_textView.attributedText = _pendingAttributedText;
|
||||
_pendingAttributedText = nil;
|
||||
_textView.selectedRange = range;
|
||||
[_textView layoutIfNeeded];
|
||||
_textView.contentOffset = contentOffset;
|
||||
|
||||
[self _setPlaceholderVisibility];
|
||||
|
||||
@ -174,11 +174,21 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
CGRect frame = UIEdgeInsetsInsetRect(self.bounds, adjustedFrameInset);
|
||||
_textView.frame = frame;
|
||||
_placeholderView.frame = frame;
|
||||
_scrollView.frame = frame;
|
||||
[self updateContentSize];
|
||||
|
||||
_textView.textContainerInset = adjustedTextContainerInset;
|
||||
_placeholderView.textContainerInset = adjustedTextContainerInset;
|
||||
}
|
||||
|
||||
- (void)updateContentSize
|
||||
{
|
||||
_textView.scrollEnabled = YES;
|
||||
_scrollView.contentSize = _textView.contentSize;
|
||||
_textView.frame = (CGRect){CGPointZero, _scrollView.contentSize};
|
||||
_textView.scrollEnabled = NO;
|
||||
}
|
||||
|
||||
- (void)updatePlaceholder
|
||||
{
|
||||
[_placeholderView removeFromSuperview];
|
||||
@ -186,6 +196,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
if (_placeholder) {
|
||||
_placeholderView = [[UITextView alloc] initWithFrame:self.bounds];
|
||||
_placeholderView.editable = NO;
|
||||
_placeholderView.userInteractionEnabled = NO;
|
||||
_placeholderView.backgroundColor = [UIColor clearColor];
|
||||
_placeholderView.scrollEnabled = false;
|
||||
_placeholderView.scrollsToTop = NO;
|
||||
@ -211,16 +223,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
[self updatePlaceholder];
|
||||
}
|
||||
|
||||
- (UIColor *)textColor
|
||||
{
|
||||
return _textView.textColor;
|
||||
}
|
||||
|
||||
- (void)setTextColor:(UIColor *)textColor
|
||||
{
|
||||
_textView.textColor = textColor;
|
||||
}
|
||||
|
||||
- (void)setPlaceholder:(NSString *)placeholder
|
||||
{
|
||||
_placeholder = placeholder;
|
||||
@ -305,10 +307,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (textView.editable && [textView isFirstResponder]) {
|
||||
[textView scrollRangeToVisible:textView.selectedRange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
@ -369,6 +367,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)textView
|
||||
{
|
||||
[self updateContentSize];
|
||||
[self _setPlaceholderVisibility];
|
||||
_nativeEventCount++;
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||
|
@ -23,21 +23,22 @@ RCT_EXPORT_MODULE()
|
||||
return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
||||
}
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType)
|
||||
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(color, textView.textColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(keyboardType, textView.keyboardType, UIKeyboardType)
|
||||
RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, textView.keyboardAppearance, UIKeyboardAppearance)
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(returnKeyType, textView.returnKeyType, UIReturnKeyType)
|
||||
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType)
|
||||
RCT_REMAP_VIEW_PROPERTY(secureTextEntry, textView.secureTextEntry, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView)
|
||||
{
|
||||
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
|
||||
|
Loading…
x
Reference in New Issue
Block a user