Skip to content

Commit 7b738c1

Browse files
committed
Revert "fix: revert "render Text in an NSTextView (#2286)" (#2676)"
This reverts commit 66f8fbe.
1 parent 66f8fbe commit 7b738c1

File tree

1 file changed

+86
-74
lines changed

1 file changed

+86
-74
lines changed

packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm

Lines changed: 86 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
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>
@@ -28,26 +25,36 @@
2825
#import "RCTConversions.h"
2926
#import "RCTFabricComponentsPlugins.h"
3027

28+
#import <QuartzCore/QuartzCore.h> // [macOS]
29+
3130
using 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 &paragraphProps = 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 &paragraphProps = 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

Comments
 (0)