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 c2cc7f38f43..d09c6f760fd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -35,6 +35,7 @@ using namespace facebook::react; ParagraphAttributes _paragraphAttributes; RCTParagraphComponentAccessibilityProvider *_accessibilityProvider; UILongPressGestureRecognizer *_longPressGestureRecognizer; + CAShapeLayer *_highlightLayer; } - (instancetype)initWithFrame:(CGRect)frame @@ -132,7 +133,21 @@ using namespace facebook::react; [nativeTextLayoutManager drawAttributedString:_state->getData().attributedString paragraphAttributes:_paragraphAttributes frame:frame - textStorage:unwrapManagedObject(nsTextStorage)]; + textStorage:unwrapManagedObject(nsTextStorage) + 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; + } + }]; } #pragma mark - Accessibility diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm index b76bb2fba04..74f5b65df13 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm @@ -285,7 +285,7 @@ NSDictionary *RCTNSTextAttributesFromTextAttributes(c } // Special - if (textAttributes.isHighlighted) { + if (textAttributes.isHighlighted.value_or(false)) { attributes[RCTAttributedStringIsHighlightedAttributeName] = @YES; } diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h index b0d906ca10c..41809ce12ae 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h @@ -43,7 +43,8 @@ using RCTTextLayoutFragmentEnumerationBlock = - (void)drawAttributedString:(facebook::react::AttributedString)attributedString paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes frame:(CGRect)frame - textStorage:(NSTextStorage *_Nullable)textStorage; + textStorage:(NSTextStorage *_Nullable)textStorage + drawHighlightPath:(void (^_Nullable)(UIBezierPath *highlightPath))block; - (facebook::react::LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedString)attributedString paragraphAttributes: diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index f7d17f52dc3..1b917150074 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -76,6 +76,7 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi paragraphAttributes:(ParagraphAttributes)paragraphAttributes frame:(CGRect)frame textStorage:(NSTextStorage *_Nullable)textStorage + drawHighlightPath:(void (^_Nullable)(UIBezierPath *highlightPath))block { BOOL createdStorageForFrame = NO; @@ -118,6 +119,38 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi #if TARGET_OS_MACCATALYST CGContextRestoreGState(context); #endif + + if (block != nil) { + __block UIBezierPath *highlightPath = nil; + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + [textStorage + enumerateAttribute:RCTAttributedStringIsHighlightedAttributeName + inRange:characterRange + options:0 + usingBlock:^(NSNumber *value, NSRange range, __unused BOOL *stop) { + if (!value.boolValue) { + return; + } + + [layoutManager + enumerateEnclosingRectsForGlyphRange:range + withinSelectedGlyphRange:range + inTextContainer:textContainer + usingBlock:^(CGRect enclosingRect, __unused BOOL *anotherStop) { + UIBezierPath *path = [UIBezierPath + bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) + cornerRadius:2]; + if (highlightPath) { + [highlightPath appendPath:path]; + } else { + highlightPath = path; + } + }]; + }]; + + block(highlightPath); + } } - (LinesMeasurements)getLinesForAttributedString:(AttributedString)attributedString