Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ @interface RCTParagraphTextView : UIView

@end

@interface RCTParagraphSelectableTextView : UITextView
@end

#if !TARGET_OS_TV
@interface RCTParagraphComponentView () <UIEditMenuInteractionDelegate>

Expand All @@ -50,6 +53,7 @@ @implementation RCTParagraphComponentView {
RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
RCTParagraphTextView *_textView;
RCTParagraphSelectableTextView *_selectableTextView;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand Down Expand Up @@ -111,9 +115,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &

if (newParagraphProps.isSelectable != oldParagraphProps.isSelectable) {
if (newParagraphProps.isSelectable) {
[self enableContextMenu];
[self _enableSelection];
} else {
[self disableContextMenu];
[self _disableSelection];
}
}

Expand All @@ -125,6 +129,10 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &
_textView.state = std::static_pointer_cast<const ParagraphShadowNode::ConcreteState>(state);
[_textView setNeedsDisplay];
[self setNeedsLayout];

if (_selectableTextView) {
[self updateSelectableTextStorage];
}
}

- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
Expand All @@ -136,11 +144,18 @@ - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
_textView.layoutMetrics = _layoutMetrics;
[_textView setNeedsDisplay];
[self setNeedsLayout];

if (_selectableTextView) {
[self updateSelectableTextStorage];
}
}

- (void)prepareForRecycle
{
[super prepareForRecycle];
if (_selectableTextView) {
[self _disableSelection];
}
_textView.state = nullptr;
_accessibilityProvider = nil;
}
Expand All @@ -149,7 +164,79 @@ - (void)layoutSubviews
{
[super layoutSubviews];

if (_selectableTextView) {
_selectableTextView.frame = self.bounds;
} else {
_textView.frame = self.bounds;
}
}

#pragma mark - Selection Management

- (void)_enableSelection
{
if (_selectableTextView) {
return;
}

_selectableTextView = [[RCTParagraphSelectableTextView alloc] initWithFrame:self.bounds];
_selectableTextView.editable = NO;
_selectableTextView.selectable = YES;
_selectableTextView.scrollEnabled = NO;
_selectableTextView.textContainerInset = UIEdgeInsetsZero;
_selectableTextView.textContainer.lineFragmentPadding = 0;
_selectableTextView.backgroundColor = [UIColor clearColor];

// Sync text content into the UITextView.
[self updateSelectableTextStorage];

// Swap: remove the default text view, install the selectable one.
[_textView removeFromSuperview];
self.contentView = _selectableTextView;

// Also enable the context menu (long press to copy).
[self enableContextMenu];
}

- (void)_disableSelection
{
if (!_selectableTextView) {
return;
}

[self disableContextMenu];

// Swap back: remove the selectable text view, restore the default one.
[_selectableTextView removeFromSuperview];
_selectableTextView = nil;

self.contentView = _textView;
_textView.frame = self.bounds;
[_textView setNeedsDisplay];
}

- (void)updateSelectableTextStorage
{
if (!_selectableTextView || !_textView.state) {
return;
}

const auto &stateData = _textView.state->getData();
auto textLayoutManager = stateData.layoutManager.lock();
if (!textLayoutManager) {
return;
}

RCTTextLayoutManager *nativeTextLayoutManager =
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());

NSTextStorage *textStorage = [nativeTextLayoutManager getTextStorageForAttributedString:stateData.attributedString
paragraphAttributes:_paragraphAttributes
size:frame.size];

_selectableTextView.attributedText = textStorage;
_selectableTextView.frame = frame;
}

#pragma mark - Accessibility
Expand Down Expand Up @@ -426,3 +513,6 @@ - (void)drawRect:(CGRect)rect
}

@end

@implementation RCTParagraphSelectableTextView
@end
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ using RCTTextLayoutFragmentEnumerationBlock =
frame:(CGRect)frame
usingBlock:(RCTTextLayoutFragmentEnumerationBlock)block;

- (NSTextStorage *)getTextStorageForAttributedString:(facebook::react::AttributedString)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
size:(CGSize)size;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,16 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage
return TextMeasurement{.size = {.width = size.width, .height = size.height}, .attachments = attachments};
}

- (NSTextStorage *)getTextStorageForAttributedString:(AttributedString)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
size:(CGSize)size
{
NSAttributedString *nsAttributedString = [self _nsAttributedStringFromAttributedString:attributedString];
NSTextStorage *textStorage = [self _textStorageAndLayoutManagerWithAttributesString:nsAttributedString
paragraphAttributes:paragraphAttributes
size:size];

return textStorage;
}

@end
Loading