Skip to content

Commit 0bd3326

Browse files
hryhoriiK97meta-codesync[bot]
authored andcommitted
Skip the clear background attribute on iOS Text (#59207)
Summary: `RCTNSTextAttributesFromTextAttributes` (`ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm`) builds the attributes TextKit draws each text fragment with. **Current behavior.** It sets `NSBackgroundColorAttributeName` whenever `textAttributes.backgroundColor` is set or `opacity` is set. `TextAttributes::defaultTextAttributes()` sets `backgroundColor` to `clearColor()`, so every fragment of every `<Text>` gets a transparent background attribute, including text that never asked for a background. On every draw, `NSLayoutManager` then computes the rects of each run carrying that attribute and fills them with the clear color, which changes no pixels. In a Time Profiler trace of mounting 1000 labels on a device, this background pass was about a third of `-[RCTTextLayoutManager drawAttributedString:...]`. **With this change.** The attribute is set only when the background color is set and is not `clearColor()`. A clear background draws nothing, so the pixels are the same, and TextKit skips the background pass for text without a background. Text with a visible background color keeps the attribute as before, including with `opacity`. A nested `<Text>` with `backgroundColor: 'transparent'` inside a parent with a background renders the same, since each fragment carries its own attributes. The other reader of the attribute, `processTruncatedAttributedText:` in `RCTTextLayoutManager`, already handles it being absent. ## Changelog: [IOS] [CHANGED] - Text without a background color no longer draws a transparent background Pull Request resolved: #59207 Test Plan: - `node scripts/clang-format.js --check` on the changed file passes. - RNTester built with `xcodebuild` in Release and run on a physical iPhone 15 Pro (iOS 26.6.1). - **Same output.** - Screenshots of a list of 1000 labels in bordered cards, with shadow props and with `boxShadow`, are byte-identical in the list area with and without the change. - In the RNTester Text examples (25 screens of scrolling, including text with background colors, borders and nested text), static screens are byte-identical with and without the change once scroll positions are aligned. Where a screen landed a few points off, only the anti-aliased corners of rounded Views differ, by at most 5 levels, and the same screen captured at the same position is byte-identical. Screens with animated content differ between two runs without the change as well. - **Measurement.** RNTester Playground mounting items in a `ScrollView`, each a `View` wrapping a `Text`, with three contents (plain, bordered card with shadow props, and the card with `boxShadow`). Timed: the total time in `-[RCTParagraphTextView drawRect:]` in the display pass after each mount, and the time from the end of the mount to the CoreAnimation commit, which contains that pass. The two arms are the same binary with the change switched by an environment variable. 20 items were mounted ten times per content per launch, in the order before, after, after, before. 1000 items were mounted five times per content per launch, in two series in opposite orders (before, after, after, before, then after, before, before, after), which gave the same numbers. The presses were driven by [Argent](https://github.com/software-mansion/argent) on the physical iPhone. iPhone 15 Pro, Release, medians: | 20 labels, 62–63 mounts per arm | before | this PR | change | |---|--:|--:|--:| | text drawing per mount | 5.06 ms | 3.58 ms | −29% | | end of mount to CoreAnimation commit | 4.4 ms | 3.4 ms | −23% | | 1000 labels, 60 mounts per arm | before | this PR | change | |---|--:|--:|--:| | text drawing per mount | 58.1 ms | 38.2 ms | −34% | | end of mount to CoreAnimation commit | 124.4 ms | 93.6 ms | −25% | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: cipolleschi Differential Revision: D126404689 Pulled By: javache fbshipit-source-id: 03fe945bdaacc438497f945858b995c285310c69
1 parent 8efebe9 commit 0bd3326

1 file changed

Lines changed: 2 additions & 1 deletion

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex
205205
attributes[NSForegroundColorAttributeName] = effectiveForegroundColor;
206206
}
207207

208-
if (textAttributes.backgroundColor || !isnan(textAttributes.opacity)) {
208+
// A clear background draws nothing, but TextKit still computes and fills its rects on every draw.
209+
if (textAttributes.backgroundColor && textAttributes.backgroundColor != clearColor()) {
209210
attributes[NSBackgroundColorAttributeName] = RCTEffectiveBackgroundColorFromTextAttributes(textAttributes);
210211
}
211212

0 commit comments

Comments
 (0)