11
11
#if !TARGET_OS_OSX // [macOS]
12
12
#import < MobileCoreServices/UTCoreTypes.h>
13
13
#endif // [macOS]
14
- #if TARGET_OS_OSX // [macOS
15
- #import < QuartzCore/CAShapeLayer.h>
16
- #endif // macOS]
17
14
18
15
#import < react/renderer/components/text/ParagraphComponentDescriptor.h>
19
16
#import < react/renderer/components/text/ParagraphProps.h>
28
25
#import " RCTConversions.h"
29
26
#import " RCTFabricComponentsPlugins.h"
30
27
28
+ #import < QuartzCore/QuartzCore.h> // [macOS]
29
+
31
30
using namespace facebook ::react;
32
31
32
+ #if !TARGET_OS_OSX // [macOS]
33
33
// ParagraphTextView is an auxiliary view we set as contentView so the drawing
34
34
// can happen on top of the layers manipulated by RCTViewComponentView (the parent view)
35
35
@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]
36
41
37
42
@property (nonatomic ) ParagraphShadowNode::ConcreteState::Shared state;
38
43
@property (nonatomic ) ParagraphAttributes paragraphAttributes;
39
44
@property (nonatomic ) LayoutMetrics layoutMetrics;
40
45
46
+ #if TARGET_OS_OSX // [macOS]
47
+ // / UIKit compatibility shim that simply calls `[self setNeedsDisplay:YES]`
48
+ - (void )setNeedsDisplay ;
49
+ #endif
50
+
41
51
@end
42
52
43
53
#if !TARGET_OS_OSX // [macOS]
44
54
@interface RCTParagraphComponentView () <UIEditMenuInteractionDelegate>
45
55
46
56
@property (nonatomic , nullable ) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE (ios(16.0 ));
47
57
48
- @end
49
- #else // [macOS
50
- @interface RCTParagraphComponentView ()
51
58
@end
52
59
#endif // [macOS]
53
60
@@ -57,7 +64,7 @@ @implementation RCTParagraphComponentView {
57
64
RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
58
65
#if !TARGET_OS_OSX // [macOS]
59
66
UILongPressGestureRecognizer *_longPressGestureRecognizer;
60
- #endif // [ macOS]
67
+ #endif // macOS]
61
68
RCTParagraphTextView *_textView;
62
69
}
63
70
@@ -66,11 +73,30 @@ - (instancetype)initWithFrame:(CGRect)frame
66
73
if (self = [super initWithFrame: frame]) {
67
74
_props = ParagraphShadowNode::defaultSharedProps ();
68
75
69
- #if !TARGET_OS_OSX // [macOS]
76
+ #if !TARGET_OS_OSX // [macOS]
70
77
self.opaque = NO ;
71
- #endif // [macOS]
72
78
_textView = [RCTParagraphTextView new ];
73
79
_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]
74
100
self.contentView = _textView;
75
101
}
76
102
@@ -127,7 +153,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
127
153
} else {
128
154
[self disableContextMenu ];
129
155
}
130
- #endif // [macOS]
156
+ #else // [macOS
157
+ _textView.selectable = newParagraphProps.isSelectable ;
158
+ #endif // macOS]
131
159
}
132
160
133
161
[super updateProps: props oldProps: oldProps];
@@ -185,7 +213,7 @@ - (BOOL)isAccessibilityElement
185
213
return NO ;
186
214
}
187
215
188
- #if !TARGET_OS_OSX // [macOS
216
+ #if !TARGET_OS_OSX // [macOS]
189
217
- (NSArray *)accessibilityElements
190
218
{
191
219
const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -221,12 +249,7 @@ - (UIAccessibilityTraits)accessibilityTraits
221
249
{
222
250
return [super accessibilityTraits ] | UIAccessibilityTraitStaticText;
223
251
}
224
- #else // [macOS
225
- - (NSAccessibilityRole )accessibilityRole
226
- {
227
- return [super accessibilityRole ] ?: NSAccessibilityStaticTextRole ;
228
- }
229
- #endif // macOS]
252
+ #endif // [macOS]
230
253
231
254
#pragma mark - RCTTouchableComponentViewProtocol
232
255
@@ -310,7 +333,6 @@ - (BOOL)canBecomeFirstResponder
310
333
return paragraphProps.isSelectable ;
311
334
}
312
335
313
- #if !TARGET_OS_OSX // [macOS]
314
336
- (BOOL )canPerformAction : (SEL )action withSender : (id )sender
315
337
{
316
338
const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -319,9 +341,12 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
319
341
return YES ;
320
342
}
321
343
344
+ #if !TARGET_OS_OSX // [macOS]
322
345
return [self .nextResponder canPerformAction: action withSender: sender];
346
+ #else // [macOS
347
+ return NO ;
348
+ #endif // macOS]
323
349
}
324
- #endif // [macOS]
325
350
326
351
- (void )copy : (id )sender
327
352
{
@@ -357,10 +382,12 @@ - (void)copy:(id)sender
357
382
}
358
383
359
384
@implementation RCTParagraphTextView {
385
+ #if !TARGET_OS_OSX // [macOS]
360
386
CAShapeLayer *_highlightLayer;
387
+ #endif // macOS]
361
388
}
362
389
363
- - (RCTUIView *)hitTest : (CGPoint)point withEvent : (UIEvent *)event
390
+ - (RCTUIView *)hitTest : (CGPoint)point withEvent : (UIEvent *)event // [macOS]
364
391
{
365
392
return nil ;
366
393
}
@@ -382,6 +409,7 @@ - (void)drawRect:(CGRect)rect
382
409
383
410
CGRect frame = RCTCGRectFromRect (_layoutMetrics.getContentFrame ());
384
411
412
+ #if !TARGET_OS_OSX // [macOS]
385
413
[nativeTextLayoutManager drawAttributedString: _state->getData ().attributedString
386
414
paragraphAttributes: _paragraphAttributes
387
415
frame: frame
@@ -393,70 +421,54 @@ - (void)drawRect:(CGRect)rect
393
421
[self .layer addSublayer: self ->_highlightLayer];
394
422
}
395
423
self->_highlightLayer .position = frame.origin ;
396
-
397
- #if !TARGET_OS_OSX // [macOS]
398
424
self->_highlightLayer .path = highlightPath.CGPath ;
399
- #else // [macOS Update once our minimum is macOS 14
400
- self->_highlightLayer .path = UIBezierPathCreateCGPathRef (highlightPath);
401
- #endif // macOS]
402
425
} else {
403
426
[self ->_highlightLayer removeFromSuperlayer ];
404
427
self->_highlightLayer = nil ;
405
428
}
406
429
}];
407
- }
430
+ #else // [macOS
431
+ NSTextStorage *textStorage = [nativeTextLayoutManager getTextStorageForAttributedString: _state->getData ().attributedString paragraphAttributes: _paragraphAttributes size: frame.size];
408
432
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
+ }
410
451
411
452
#if TARGET_OS_OSX // [macOS
412
- // Copied from RCTUIKit
413
- CGPathRef UIBezierPathCreateCGPathRef (UIBezierPath *bezierPath)
453
+ - (void )setNeedsDisplay
414
454
{
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 ;
458
468
}
459
-
460
- return immutablePath ;
469
+
470
+ return [ super resignFirstResponder ] ;
461
471
}
462
472
#endif // macOS]
473
+
474
+ @end
0 commit comments