From f4609dbb5fd8b2db986b9341ab866d03a56c8640 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Tue, 20 Aug 2024 04:38:45 -0700 Subject: [PATCH] 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 --- .../Text/RCTParagraphComponentView.mm | 110 ++++++++++++------ 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index d2f90b4afad..9eaf45a4b22 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -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 () @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(*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(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 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