1111#if !TARGET_OS_OSX // [macOS]
1212#import < MobileCoreServices/UTCoreTypes.h>
1313#endif // [macOS]
14- #if TARGET_OS_OSX // [macOS
15- #import < QuartzCore/CAShapeLayer.h>
16- #endif // macOS]
1714
1815#import < react/renderer/components/text/ParagraphComponentDescriptor.h>
1916#import < react/renderer/components/text/ParagraphProps.h>
2825#import " RCTConversions.h"
2926#import " RCTFabricComponentsPlugins.h"
3027
28+ #import < QuartzCore/QuartzCore.h> // [macOS]
29+
3130using namespace facebook ::react;
3231
32+ #if !TARGET_OS_OSX // [macOS]
3333// ParagraphTextView is an auxiliary view we set as contentView so the drawing
3434// can happen on top of the layers manipulated by RCTViewComponentView (the parent view)
3535@interface RCTParagraphTextView : RCTUIView // [macOS]
36+ #else // [macOS
37+ // On macOS, we also defer drawing to an NSTextView,
38+ // in order to get more native behaviors like text selection.
39+ @interface RCTParagraphTextView : NSTextView // [macOS]
40+ #endif // macOS]
3641
3742@property (nonatomic ) ParagraphShadowNode::ConcreteState::Shared state;
3843@property (nonatomic ) ParagraphAttributes paragraphAttributes;
3944@property (nonatomic ) LayoutMetrics layoutMetrics;
4045
46+ #if TARGET_OS_OSX // [macOS]
47+ // / UIKit compatibility shim that simply calls `[self setNeedsDisplay:YES]`
48+ - (void )setNeedsDisplay ;
49+ #endif
50+
4151@end
4252
4353#if !TARGET_OS_OSX // [macOS]
4454@interface RCTParagraphComponentView () <UIEditMenuInteractionDelegate>
4555
4656@property (nonatomic , nullable ) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE (ios(16.0 ));
4757
48- @end
49- #else // [macOS
50- @interface RCTParagraphComponentView ()
5158@end
5259#endif // [macOS]
5360
@@ -57,7 +64,7 @@ @implementation RCTParagraphComponentView {
5764 RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
5865#if !TARGET_OS_OSX // [macOS]
5966 UILongPressGestureRecognizer *_longPressGestureRecognizer;
60- #endif // [ macOS]
67+ #endif // macOS]
6168 RCTParagraphTextView *_textView;
6269}
6370
@@ -66,11 +73,30 @@ - (instancetype)initWithFrame:(CGRect)frame
6673 if (self = [super initWithFrame: frame]) {
6774 _props = ParagraphShadowNode::defaultSharedProps ();
6875
69- #if !TARGET_OS_OSX // [macOS]
76+ #if !TARGET_OS_OSX // [macOS]
7077 self.opaque = NO ;
71- #endif // [macOS]
7278 _textView = [RCTParagraphTextView new ];
7379 _textView.backgroundColor = RCTUIColor.clearColor ; // [macOS]
80+ #else // [macOS
81+ // Make the RCTParagraphComponentView accessible and available in the a11y hierarchy.
82+ self.accessibilityElement = YES ;
83+ self.accessibilityRole = NSAccessibilityStaticTextRole ;
84+ // Fix blurry text on non-retina displays.
85+ self.canDrawSubviewsIntoLayer = YES ;
86+ // The NSTextView is responsible for drawing text and managing selection.
87+ _textView = [[RCTParagraphTextView alloc ] initWithFrame: self .bounds];
88+ // The RCTParagraphComponentUnfocusableTextView is only used for rendering and should not appear in the a11y hierarchy.
89+ _textView.accessibilityElement = NO ;
90+ _textView.usesFontPanel = NO ;
91+ _textView.drawsBackground = NO ;
92+ _textView.linkTextAttributes = @{};
93+ _textView.editable = NO ;
94+ _textView.selectable = NO ;
95+ _textView.verticallyResizable = NO ;
96+ _textView.layoutManager .usesFontLeading = NO ;
97+ self.contentView = _textView;
98+ self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
99+ #endif // macOS]
74100 self.contentView = _textView;
75101 }
76102
@@ -127,7 +153,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
127153 } else {
128154 [self disableContextMenu ];
129155 }
130- #endif // [macOS]
156+ #else // [macOS
157+ _textView.selectable = newParagraphProps.isSelectable ;
158+ #endif // macOS]
131159 }
132160
133161 [super updateProps: props oldProps: oldProps];
@@ -185,7 +213,7 @@ - (BOOL)isAccessibilityElement
185213 return NO ;
186214}
187215
188- #if !TARGET_OS_OSX // [macOS
216+ #if !TARGET_OS_OSX // [macOS]
189217- (NSArray *)accessibilityElements
190218{
191219 const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -221,12 +249,7 @@ - (UIAccessibilityTraits)accessibilityTraits
221249{
222250 return [super accessibilityTraits ] | UIAccessibilityTraitStaticText;
223251}
224- #else // [macOS
225- - (NSAccessibilityRole )accessibilityRole
226- {
227- return [super accessibilityRole ] ?: NSAccessibilityStaticTextRole ;
228- }
229- #endif // macOS]
252+ #endif // [macOS]
230253
231254#pragma mark - RCTTouchableComponentViewProtocol
232255
@@ -310,7 +333,6 @@ - (BOOL)canBecomeFirstResponder
310333 return paragraphProps.isSelectable ;
311334}
312335
313- #if !TARGET_OS_OSX // [macOS]
314336- (BOOL )canPerformAction : (SEL )action withSender : (id )sender
315337{
316338 const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -319,9 +341,12 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
319341 return YES ;
320342 }
321343
344+ #if !TARGET_OS_OSX // [macOS]
322345 return [self .nextResponder canPerformAction: action withSender: sender];
346+ #else // [macOS
347+ return NO ;
348+ #endif // macOS]
323349}
324- #endif // [macOS]
325350
326351- (void )copy : (id )sender
327352{
@@ -357,10 +382,12 @@ - (void)copy:(id)sender
357382}
358383
359384@implementation RCTParagraphTextView {
385+ #if !TARGET_OS_OSX // [macOS]
360386 CAShapeLayer *_highlightLayer;
387+ #endif // macOS]
361388}
362389
363- - (RCTUIView *)hitTest : (CGPoint)point withEvent : (UIEvent *)event
390+ - (RCTUIView *)hitTest : (CGPoint)point withEvent : (UIEvent *)event // [macOS]
364391{
365392 return nil ;
366393}
@@ -382,6 +409,7 @@ - (void)drawRect:(CGRect)rect
382409
383410 CGRect frame = RCTCGRectFromRect (_layoutMetrics.getContentFrame ());
384411
412+ #if !TARGET_OS_OSX // [macOS]
385413 [nativeTextLayoutManager drawAttributedString: _state->getData ().attributedString
386414 paragraphAttributes: _paragraphAttributes
387415 frame: frame
@@ -393,70 +421,54 @@ - (void)drawRect:(CGRect)rect
393421 [self .layer addSublayer: self ->_highlightLayer];
394422 }
395423 self->_highlightLayer .position = frame.origin ;
396-
397- #if !TARGET_OS_OSX // [macOS]
398424 self->_highlightLayer .path = highlightPath.CGPath ;
399- #else // [macOS Update once our minimum is macOS 14
400- self->_highlightLayer .path = UIBezierPathCreateCGPathRef (highlightPath);
401- #endif // macOS]
402425 } else {
403426 [self ->_highlightLayer removeFromSuperlayer ];
404427 self->_highlightLayer = nil ;
405428 }
406429 }];
407- }
430+ #else // [macOS
431+ NSTextStorage *textStorage = [nativeTextLayoutManager getTextStorageForAttributedString: _state->getData ().attributedString paragraphAttributes: _paragraphAttributes size: frame.size];
408432
409- @end
433+ NSLayoutManager *layoutManager = textStorage.layoutManagers .firstObject ;
434+ NSTextContainer *textContainer = layoutManager.textContainers .firstObject ;
435+
436+ [self replaceTextContainer: textContainer];
437+
438+ NSArray <NSLayoutManager *> *managers = [[textStorage layoutManagers ] copy ];
439+ for (NSLayoutManager *manager in managers) {
440+ [textStorage removeLayoutManager: manager];
441+ }
442+
443+ self.minSize = frame.size ;
444+ self.maxSize = frame.size ;
445+ self.frame = frame;
446+ [[self textStorage ] setAttributedString: textStorage];
447+
448+ [super drawRect: rect];
449+ #endif
450+ }
410451
411452#if TARGET_OS_OSX // [macOS
412- // Copied from RCTUIKit
413- CGPathRef UIBezierPathCreateCGPathRef (UIBezierPath *bezierPath)
453+ - (void )setNeedsDisplay
414454{
415- CGPathRef immutablePath = NULL ;
416-
417- // Draw the path elements.
418- NSInteger numElements = [bezierPath elementCount ];
419- if (numElements > 0 )
420- {
421- CGMutablePathRef path = CGPathCreateMutable ();
422- NSPoint points[3 ];
423- BOOL didClosePath = YES ;
424-
425- for (NSInteger i = 0 ; i < numElements; i++)
426- {
427- switch ([bezierPath elementAtIndex: i associatedPoints: points])
428- {
429- case NSBezierPathElementMoveTo:
430- CGPathMoveToPoint (path, NULL , points[0 ].x , points[0 ].y );
431- break ;
432-
433- case NSBezierPathElementLineTo:
434- CGPathAddLineToPoint (path, NULL , points[0 ].x , points[0 ].y );
435- didClosePath = NO ;
436- break ;
437-
438- case NSBezierPathElementCurveTo:
439- CGPathAddCurveToPoint (path, NULL , points[0 ].x , points[0 ].y ,
440- points[1 ].x , points[1 ].y ,
441- points[2 ].x , points[2 ].y );
442- didClosePath = NO ;
443- break ;
444-
445- case NSBezierPathElementClosePath:
446- CGPathCloseSubpath (path);
447- didClosePath = YES ;
448- break ;
449- }
450- }
451-
452- // Be sure the path is closed or Quartz may not do valid hit detection.
453- if (!didClosePath)
454- CGPathCloseSubpath (path);
455-
456- immutablePath = CGPathCreateCopy (path);
457- CGPathRelease (path);
455+ [self setNeedsDisplay: YES ];
456+ }
457+
458+ - (BOOL )canBecomeKeyView
459+ {
460+ return NO ;
461+ }
462+
463+ - (BOOL )resignFirstResponder
464+ {
465+ // Don't relinquish first responder while selecting text.
466+ if (self.selectable && NSRunLoop .currentRunLoop .currentMode == NSEventTrackingRunLoopMode ) {
467+ return NO ;
458468 }
459-
460- return immutablePath ;
469+
470+ return [ super resignFirstResponder ] ;
461471}
462472#endif // macOS]
473+
474+ @end
0 commit comments