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:
Nick Lockwood 2015-11-24 14:26:14 -08:00 committed by facebook-github-bot-7
parent 469a65217c
commit b5be05d82b
4 changed files with 74 additions and 75 deletions

View File

@ -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 />; },

View File

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

View File

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

View File

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