mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-26 23:05:00 +08:00
Added rich text input support
Summary: public It is now possible to display and edit rich text inside a multiline `<textInput>` by nesting a `<Text>` node inside it. Note that this doesn't yet provide everything needed to build a full rich text editor (as there is no facility to capture or control the selected text range, or insert/remove text) but it does make it possible to apply token-based styling to text as the user types. See the 'Attributed text' example in the UIExplorer > TextInput demo for details. Reviewed By: javache Differential Revision: D2622493 fb-gh-sync-id: b6bc9a46005322c806934541966460edccb59e70
This commit is contained in:
committed by
facebook-github-bot-8
parent
8dac41b7f0
commit
7779e06a7f
@@ -11,6 +11,7 @@
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTText.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
@@ -38,6 +39,10 @@
|
||||
UITextView *_placeholderView;
|
||||
UITextView *_textView;
|
||||
NSInteger _nativeEventCount;
|
||||
RCTText *_richTextView;
|
||||
NSAttributedString *_pendingAttributedText;
|
||||
NSMutableArray<UIView<RCTComponent> *> *_subviews;
|
||||
BOOL _blockTextShouldChange;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
@@ -53,6 +58,8 @@
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.scrollsToTop = NO;
|
||||
_textView.delegate = self;
|
||||
|
||||
_subviews = [NSMutableArray new];
|
||||
[self addSubview:_textView];
|
||||
}
|
||||
return self;
|
||||
@@ -61,6 +68,90 @@
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (NSArray<UIView<RCTComponent> *> *)reactSubviews
|
||||
{
|
||||
return _subviews;
|
||||
}
|
||||
|
||||
- (void)insertReactSubview:(UIView<RCTComponent> *)subview atIndex:(NSInteger)index
|
||||
{
|
||||
if ([subview isKindOfClass:[RCTText class]]) {
|
||||
if (_richTextView) {
|
||||
RCTLogError(@"Tried to insert a second <Text> into <TextInput> - there can only be one.");
|
||||
}
|
||||
_richTextView = (RCTText *)subview;
|
||||
[_subviews insertObject:_richTextView atIndex:index];
|
||||
} else {
|
||||
[_subviews insertObject:subview atIndex:index];
|
||||
[self insertSubview:subview atIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView<RCTComponent> *)subview
|
||||
{
|
||||
if (_richTextView == subview) {
|
||||
[_subviews removeObject:_richTextView];
|
||||
_richTextView = nil;
|
||||
} else {
|
||||
[_subviews removeObject:subview];
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMostRecentEventCount:(NSInteger)mostRecentEventCount
|
||||
{
|
||||
_mostRecentEventCount = mostRecentEventCount;
|
||||
|
||||
// Props are set after uiBlockToAmendWithShadowViewRegistry, which means that
|
||||
// at the time performTextUpdate is called, _mostRecentEventCount will be
|
||||
// behind _eventCount, with the result that performPendingTextUpdate will do
|
||||
// nothing. For that reason we call it again here after mostRecentEventCount
|
||||
// has been set.
|
||||
[self performPendingTextUpdate];
|
||||
}
|
||||
|
||||
- (void)performTextUpdate
|
||||
{
|
||||
if (_richTextView) {
|
||||
_pendingAttributedText = _richTextView.textStorage;
|
||||
[self performPendingTextUpdate];
|
||||
} else if (!self.text) {
|
||||
_textView.attributedText = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performPendingTextUpdate
|
||||
{
|
||||
if (!_pendingAttributedText || _mostRecentEventCount < _nativeEventCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([_textView.attributedText isEqualToAttributedString:_pendingAttributedText]) {
|
||||
_pendingAttributedText = nil; // Don't try again.
|
||||
return;
|
||||
}
|
||||
|
||||
// When we update the attributed text, there might be pending autocorrections
|
||||
// that will get accepted by default. In order for this to not garble our text,
|
||||
// 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];
|
||||
|
||||
_blockTextShouldChange = NO;
|
||||
}
|
||||
|
||||
- (void)updateFrames
|
||||
{
|
||||
// Adjust the insets so that they are as close as possible to single-line
|
||||
@@ -156,6 +247,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
||||
{
|
||||
if (_blockTextShouldChange) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (textView.textWasPasted) {
|
||||
textView.textWasPasted = NO;
|
||||
} else {
|
||||
@@ -309,9 +404,4 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
return [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22];
|
||||
}
|
||||
|
||||
- (void)performTextUpdate
|
||||
{
|
||||
// Not used (yet)
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user