Skip to content

Commit 37b84be

Browse files
hryhoriiK97claude
andcommitted
Avoid copying text fragments and re-attributing runs when building NSAttributedString
`RCTNSAttributedStringFromAttributedString` iterated the fragments by value, copying each one (its string, its text attributes and a shadow view with three shared pointers), and attached the event emitter to a text fragment in a second `addAttributes:range:` pass over the string it had just created with its attributes, which splits and re-merges the attribute runs. Iterate by reference and put the emitter into a text fragment's initial attribute dictionary. Attachment fragments keep the existing second pass, so the attributes on every run are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 96dddb0 commit 37b84be

1 file changed

Lines changed: 16 additions & 8 deletions

File tree

  • packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager

‎packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
403403

404404
static NSMutableAttributedString *RCTNSAttributedStringFragmentFromFragment(
405405
const AttributedString::Fragment &fragment,
406-
UIImage *placeholderImage)
406+
UIImage *placeholderImage,
407+
NSData *_Nullable eventEmitterWrapper)
407408
{
408409
if (fragment.isAttachment()) {
409410
auto layoutMetrics = fragment.parentShadowView.layoutMetrics;
@@ -427,21 +428,28 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
427428
string = RCTNSStringFromStringApplyingTextTransform(string, textTransform);
428429
}
429430

430-
return [[NSMutableAttributedString alloc]
431-
initWithString:string
432-
attributes:RCTNSTextAttributesFromTextAttributes(fragment.textAttributes)];
431+
// Set with the initial attributes; adding it afterwards would re-split the runs.
432+
NSMutableDictionary<NSAttributedStringKey, id> *attributes =
433+
RCTNSTextAttributesFromTextAttributes(fragment.textAttributes);
434+
if (eventEmitterWrapper != nil) {
435+
attributes[RCTAttributedStringEventEmitterKey] = eventEmitterWrapper;
436+
}
437+
438+
return [[NSMutableAttributedString alloc] initWithString:string attributes:attributes];
433439
}
434440
}
435441

436442
static NSMutableAttributedString *RCTNSAttributedStringFragmentWithAttributesFromFragment(
437443
const AttributedString::Fragment &fragment,
438444
UIImage *placeholderImage)
439445
{
440-
auto nsAttributedStringFragment = RCTNSAttributedStringFragmentFromFragment(fragment, placeholderImage);
446+
NSData *eventEmitterWrapper =
447+
fragment.parentShadowView.componentHandle ? RCTWrapEventEmitter(fragment.parentShadowView.eventEmitter) : nil;
441448

442-
if (fragment.parentShadowView.componentHandle) {
443-
auto eventEmitterWrapper = RCTWrapEventEmitter(fragment.parentShadowView.eventEmitter);
449+
auto nsAttributedStringFragment =
450+
RCTNSAttributedStringFragmentFromFragment(fragment, placeholderImage, eventEmitterWrapper);
444451

452+
if (fragment.isAttachment() && eventEmitterWrapper != nil) {
445453
NSDictionary<NSAttributedStringKey, id> *additionalTextAttributes =
446454
@{RCTAttributedStringEventEmitterKey : eventEmitterWrapper};
447455

@@ -464,7 +472,7 @@ void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
464472

465473
[nsAttributedString beginEditing];
466474

467-
for (auto fragment : attributedString.getFragments()) {
475+
for (const auto &fragment : attributedString.getFragments()) {
468476
NSMutableAttributedString *nsAttributedStringFragment =
469477
RCTNSAttributedStringFragmentWithAttributesFromFragment(fragment, placeholderImage);
470478

0 commit comments

Comments
 (0)