Skip to content

Share iOS text measurements that differ only in unused constraints - #59527

Open
hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:ios-text-measure-cache-key
Open

hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:ios-text-measure-cache-key

Conversation

@hryhoriiK97

Copy link
Copy Markdown
Contributor

Summary:

On iOS, TextLayoutManager::measure looks up each Text measurement in TextMeasureCache, keyed by the attributed string, the paragraph attributes and the full LayoutConstraints.

Current behavior. The native measurement (-[RCTTextLayoutManager measureNSAttributedString:…]) only reads the maximum width: it lays the text out in a container of {maximumSize.width, CGFLOAT_MAX}. The minimum size and the maximum height don't change the result, and measure clamps the result to the full constraints after the cache. But they are part of the cache key, so measurements that differ only in them miss the cache and lay the same text out again with TextKit.

This happens when Yoga measures a Text more than once in a layout pass. For example, a Text in a wrapping row is measured twice with an at-most width and once with an exact width. The two at-most measurements have the same maximum width but different constraints otherwise, so both miss for new text.

With this change. The cache key leaves out the minimum size and the maximum height. Measurements that only differ in them share an entry. The native measurement still receives the original constraints, and the clamp after the cache still uses them, so every caller gets the same size as before.

Android already ignores the maximum height for the same reason (platform/android/.../TextLayoutManager.cpp: "We assume max height will have no effect on measurement, so we override it … to enable cache reuse"). On iOS the minimum size can be left out too, because the native measurement never reads it.

Changelog:

[IOS] [CHANGED] - Text measurements that differ only in the minimum size or maximum height share a cache entry

Test Plan:

  • RNTester built with xcodebuild in Release and run on a physical iPhone 15 Pro (iOS 26.6.1), from main at 4420343.
  • Measurement. RNTester Playground mounting 1,000 short <Text> labels in a wrapping row (flexDirection: 'row', flexWrap: 'wrap'), 10 rounds per run, and updating the text of the same 1,000 labels 30 times per run. Time from setState to the layout effect, which includes React rendering, layout and text measurement. "Before" and the first "this PR" runs are the same binary with the cache key switched by a launch argument (local only, not part of this PR); the last "this PR" run of each row is a build of exactly this change. The presses were driven by Argent on the physical iPhone. Medians per run:
before this PR change
Mount 1,000 labels 87.9–92.5 ms (3 runs) 68.2–73.6 ms (2 runs) −16% to −22%
Update 1,000 labels 82.7–86.0 ms (2 runs) 63.0–64.7 ms (3 runs) −22% to −27%
  • Cache counters (local logging, not part of this PR), 30,000 measurements during the mount runs: 29,998 misses before and 19,998 with this change. Each label is measured 3 times per layout pass; the two at-most measurements now share an entry.
  • Labels in a plain column are measured once per pass, so their cost doesn't change.
  • Not run: the repository's pinned clang-format, which needs dotslash.

🤖 Generated with Claude Code

The native iOS measurement depends only on the maximum width, and the
result is clamped to the full constraints after the cache, so the
minimum size and the maximum height are left out of the cache key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 27, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Oct 27, 2026
@github-actions

Copy link
Copy Markdown

Visual regression

1 visual difference(s) at abd34560817a. Visual differences need maintainer approval. Review the report, then apply visual-change-approved.

android: no capture

ios button/default-styling/default: Changed, 38223 px (1.533%)

diff

Expected and Captured
Expected Captured
expected captured

@hryhoriiK97

Copy link
Copy Markdown
Contributor Author

I checked the iOS screenshot for button/default-styling/default. The capture from this PR looks right: it shows the "Submit Application" button, like in the example. The image on main looks wrong: there is only a black square and no button. Could a maintainer apply visual-change-approved?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant