Move RCTParagraphTextView drawing to contentView (#46081)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/46081

This change fixes an issue that has been reported by OSS where a Text with both background color and borderWidth is not rendered properly.

The reason is that `RCTParagraphComponentView` uses the `drawRect` method which draws the text in the main view layer, while the parent `RCTViewComponentView` can apply an extraLayer on top of the base layer, drawing on top of the text.

This change moves the drawing of the text to an auxiliary view, `RCTParagraphTextView`, that is set as contentView of the `RCTParagraphView`. In this way, the text is drawn in a different view and can't be covered by the `_borderLayer`

## Changelog:
[Internal] - Introduce a RCTParagraphTextView to draw the text

Reviewed By: joevilches

Differential Revision: D61431369

fbshipit-source-id: 05467167186411fe42312f2ed956f5b5336de019
This commit is contained in:
Riccardo Cipolleschi
2024-08-20 04:38:45 -07:00
committed by Facebook GitHub Bot
parent 0f4a405f28
commit f4609dbb5f
@@ -24,6 +24,16 @@
using namespace facebook::react;
// ParagraphTextView is an auxiliary view we set as contentView so the drawing
// can happen on top of the layers manipulated by RCTViewComponentView (the parent view)
@interface RCTParagraphTextView : UIView
@property (nonatomic) ParagraphShadowNode::ConcreteState::Shared state;
@property (nonatomic) ParagraphAttributes paragraphAttributes;
@property (nonatomic) LayoutMetrics layoutMetrics;
@end
@interface RCTParagraphComponentView () <UIEditMenuInteractionDelegate>
@property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0));
@@ -35,7 +45,7 @@ using namespace facebook::react;
ParagraphAttributes _paragraphAttributes;
RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
CAShapeLayer *_highlightLayer;
RCTParagraphTextView *_textView;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -45,6 +55,9 @@ using namespace facebook::react;
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
_textView = [RCTParagraphTextView new];
_textView.backgroundColor = UIColor.clearColor;
self.contentView = _textView;
}
return self;
@@ -91,6 +104,7 @@ using namespace facebook::react;
const auto &newParagraphProps = static_cast<const ParagraphProps &>(*props);
_paragraphAttributes = newParagraphProps.paragraphAttributes;
_textView.paragraphAttributes = _paragraphAttributes;
if (newParagraphProps.isSelectable != oldParagraphProps.isSelectable) {
if (newParagraphProps.isSelectable) {
@@ -106,7 +120,20 @@ using namespace facebook::react;
- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState
{
_state = std::static_pointer_cast<const ParagraphShadowNode::ConcreteState>(state);
[self setNeedsDisplay];
_textView.state = _state;
[_textView setNeedsDisplay];
[self setNeedsLayout];
}
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
{
// Using stored `_layoutMetrics` as `oldLayoutMetrics` here to avoid
// re-applying individual sub-values which weren't changed.
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics];
_textView.layoutMetrics = _layoutMetrics;
[_textView setNeedsDisplay];
[self setNeedsLayout];
}
- (void)prepareForRecycle
@@ -116,40 +143,11 @@ using namespace facebook::react;
_accessibilityProvider = nil;
}
- (void)drawRect:(CGRect)rect
- (void)layoutSubviews
{
if (!_state) {
return;
}
[super layoutSubviews];
auto textLayoutManager = _state->getData().layoutManager.lock();
if (!textLayoutManager) {
return;
}
RCTTextLayoutManager *nativeTextLayoutManager =
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
[nativeTextLayoutManager drawAttributedString:_state->getData().attributedString
paragraphAttributes:_paragraphAttributes
frame:frame
drawHighlightPath:^(UIBezierPath *highlightPath) {
if (highlightPath) {
if (!self->_highlightLayer) {
self->_highlightLayer = [CAShapeLayer layer];
self->_highlightLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.25].CGColor;
[self.layer addSublayer:self->_highlightLayer];
}
self->_highlightLayer.position = frame.origin;
self->_highlightLayer.path = highlightPath.CGPath;
} else {
[self->_highlightLayer removeFromSuperlayer];
self->_highlightLayer = nil;
}
}];
_textView.frame = self.bounds;
}
#pragma mark - Accessibility
@@ -324,3 +322,47 @@ Class<RCTComponentViewProtocol> RCTParagraphCls(void)
{
return RCTParagraphComponentView.class;
}
@implementation RCTParagraphTextView {
RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
CAShapeLayer *_highlightLayer;
}
- (void)drawRect:(CGRect)rect
{
if (!_state) {
return;
}
auto textLayoutManager = _state->getData().layoutManager.lock();
if (!textLayoutManager) {
return;
}
RCTTextLayoutManager *nativeTextLayoutManager =
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
[nativeTextLayoutManager drawAttributedString:_state->getData().attributedString
paragraphAttributes:_paragraphAttributes
frame:frame
drawHighlightPath:^(UIBezierPath *highlightPath) {
if (highlightPath) {
if (!self->_highlightLayer) {
self->_highlightLayer = [CAShapeLayer layer];
self->_highlightLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.25].CGColor;
[self.layer addSublayer:self->_highlightLayer];
}
self->_highlightLayer.position = frame.origin;
self->_highlightLayer.path = highlightPath.CGPath;
} else {
[self->_highlightLayer removeFromSuperlayer];
self->_highlightLayer = nil;
}
}];
}
@end