Repository navigation
Commit 4b0a6eb
Fix zero-width "I" on iOS after an empty Text is measured (#58798)
Summary:
On iOS, a `<Text>` whose entire content is `I` is laid out zero wide, and so is never drawn, if an empty `<Text>` with the same text attributes and layout constraints was measured earlier.
`TextLayoutManager::measure` replaces an empty attributed string with a placeholder via `ensurePlaceholderIfEmpty_DO_NOT_USE`, and that placeholder string is `"I"`. The measure cache key is built from the placeholder, so an empty string and a real `"I"` share one `textMeasureCache_` entry. The compensation for the empty case (`measurement.size.width = 0`) runs inside the lambda that produces the cached value, so the zero width is what gets stored, and a later real `"I"` reads it back.
The compensation was added in #55472 (537d2a6) to fix #55468, where an empty `<Text>` took up the width of the placeholder. Before it existed the shared entry was harmless, because both lookups got the placeholder's real width. 0.83.6 does not have it and is not affected; 0.86.0 through 0.86.3 and `main` are.
This PR applies the compensation after the cache lookup instead of inside the cached value, so the cache always holds the true measurement for its key. Behavior for empty strings is unchanged.
## Changelog:
[IOS] [FIXED] - Fix `Text` containing only "I" being measured zero wide after an empty `Text` with the same style was measured
Pull Request resolved: #58798
Test Plan:
### Repro (run on React Native 0.86.3, New Architecture, iPhone Air simulator on iOS 27.0)
1. Render this component as a screen in a freshly launched app (a fresh launch matters, so the text measure cache has no entry for this style yet):
```tsx
import { Text, View } from "react-native";
const style = { fontSize: 41, color: "white" } as const;
export default function Repro() {
return (
<View style={{ flex: 1, alignItems: "center", paddingTop: 200, backgroundColor: "black" }}>
<Text style={style} onLayout={(e) => console.log("empty", e.nativeEvent.layout.width)}></Text>
<Text style={style} onLayout={(e) => console.log("I", e.nativeEvent.layout.width)}>I</Text>
<Text style={style} onLayout={(e) => console.log("A", e.nativeEvent.layout.width)}>A</Text>
</View>
);
}
```
2. Look at the screen and the logged widths.
Actual: only `A` is drawn. Logged widths:
```
empty 0
I 0
A 26.666656494140625
```
Expected: `I` and `A` are both drawn, and `I` has a non-zero width.
3. Remove the empty `<Text>` and relaunch: `I` is drawn.
### How we hit it
A letter tile that renders `<Text style={tileText}>{letter}</Text>`, where `letter` starts as `""` and becomes the typed letter. Typing any letter except `I` works. For `I` (AvenirNext-Bold, 33.6pt, inside a 56.7pt box with `alignItems: "center"`) the text reported:
```
onTextLayout: [{ text: "I", width: 10.68, height: 41.16 }]
onLayout: { x: 28.33, width: 0, height: 41.33 }
```
The same happened on a physical device on iOS 26.6 with a release build. Not rendering the empty `Text` made `I` appear, which matches the cache explanation above.
### Verification of this change
I have not yet run a build of React Native from source with this change (the app above links the prebuilt React Native core). The repro and the root cause are verified as described; the fix itself is verified by reading the code only. I'm happy to run the repro in RNTester and attach the result if that helps.
Reviewed By: javache, cipolleschi
Differential Revision: D126750629
Pulled By: zeyap
fbshipit-source-id: 2133801bb4c6b5a54c826850074c185ef7d265031 parent 7b33c40 commit 4b0a6eb
1 file changed
Lines changed: 12 additions & 8 deletions
File tree
- packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | 72 | | |
81 | 73 | | |
82 | 74 | | |
83 | 75 | | |
84 | 76 | | |
85 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
86 | 90 | | |
87 | 91 | | |
88 | 92 | | |
| |||
0 commit comments