Repository navigation
iOS: Text drops its last line when its box crosses y = 2048 on a 3x screen (Yoga float rounding) #58970
Description
Description
On iOS with the New Architecture, a <Text> that wraps to two or more lines draws one line short (the last line is laid out as a clipped single line) when its box crosses y = 2048 in the scroll content on a 3x screen. It depends on the box's position, not on the text.
Two things combine:
- Yoga rounds the height one float step short.
roundLayoutResultsToPixelGridcomputes a node's height asround(absoluteBottom) − round(absoluteTop), androundValueToPixelGridreturnsfloat, so the subtraction is done in float. With the top below 2048 and the bottom above, the two edges are quantized with different steps (2⁻¹³ vs 2⁻¹²) and the result can be43.9998779296875instead of44for two lines oflineHeight: 22.onLayoutreports exactly that height. - TextKit then drops the last line.
RCTParagraphComponentViewpasses_layoutMetrics.getContentFrame()straight toRCTTextLayoutManageras theNSTextContainersize, for drawing and foronTextLayout. Two lines need 44.0, so the second line "doesn't fit";RCTTextLayoutManagersetstextContainer.lineBreakMode = NSLineBreakByClippingwhennumberOfLinesis unset, so the last visible line is laid out without wrapping and clipped at the edge.onTextLayoutreports 1 line whose width equals the container width and whose text is the whole paragraph. WithnumberOfLinesset it shows the same single line with "…".
Measurement is unaffected (it measures with an unbounded height), so the row reserves the right space; only the draw is short. Any wrapped text with an explicit lineHeight whose box straddles 2048 (or 4096, …) in the content can hit it. We hit it in a production Expo SDK 57 app on an iPhone 16 Pro, in a list far down a screen.
Fix for part 1 proposed in Yoga: react/yoga#2037 (round in double, subtract, then narrow to float). Part 2 could be hardened separately, e.g. by rounding the container height up to the pixel grid before handing it to TextKit.
Steps to reproduce
- Run the
RNTesterPlayground.jsfrom the reproducer link in RNTester on a 3x iOS simulator or device (e.g. iPhone 16 Pro), New Architecture. - It renders 12 two-line
<Text>s positioned absolutely at tops 1896 / 1940 / 1984 / 2028 (+0, +⅓, +⅔) inside a 2300pt-tall view, and scrolls to them. - The texts whose box crosses y = 2048 on a fraction (here "top 2028+1/3" and "top 2028+2/3") show only their first line. The console logs their
onLayoutheight43.9998779296875andonTextLayoutline count1. The others show "second line" and log nothing.
Expected: every box shows both lines; a box measured at 44 is laid out at 44.
React Native Version
0.86.3 (also present on main: ReactCommon/yoga/yoga/algorithm/PixelGrid.cpp and RCTParagraphComponentView.mm are unchanged in the relevant lines)
Affected Platforms
Runtime - iOS, Build - MacOS
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
System:
OS: macOS 26.4
CPU: (14) arm64 Apple M4 Max
Memory: 424.95 MB / 36.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 22.22.2
path: /opt/homebrew/bin/node
Yarn:
version: 1.22.22
path: /opt/homebrew/bin/yarn
npm:
version: 10.9.7
path: /opt/homebrew/bin/npm
Watchman: Not Found
Managers:
CocoaPods:
version: 1.17.0
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 25.4
- iOS 26.4
- macOS 26.4
- tvOS 26.4
- visionOS 26.4
- watchOS 26.4
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Xcode:
version: 26.4.1/17E202
path: /usr/bin/xcodebuild
Languages:
Java: Not Found
npmPackages:
react-native: 0.86.3 (Expo SDK 57, expo-dev-client, New Architecture)
Tested on the iOS 26.4 simulator, iPhone 16 Pro (3x), and seen on a physical iPhone in production.
Stacktrace or Logs
box top=2028.3333333333333: 1 line(s)
box top=2028.3333333333333: height 43.9998779296875
box top=2028.6666666666667: 1 line(s)
box top=2028.6666666666667: height 43.9998779296875
From the production app, the same <Text> in two positions (same style, same string):
DBG layout speaker {"x":0,"y":23.67,"width":292,"height":43.9998779296875}
DBG lines speaker 1 [[0,0,292,22,"Give a talk at an AIE.F event. Ask the organizers for a slot."]]
DBG layout barter {"x":0,"y":23.67,"width":292,"height":44}
DBG lines barter 2 [[0,0,272,22,"Check in to an event at Barter Community "],[0,22,139,22,"Hub, where it started."]]
MANDATORY Reproducer
Pull request replacing RNTesterPlayground.js, as asked: #58972 (draft, not for merging). The same file as a gist: https://gist.github.com/ybelakov/78912e731669264e74d50add13f2a6be
Screenshots and Videos
See the screenshot in the comments: the two boxes crossing 2048 on a fraction have no "second line".
Workaround
A tiny bottom padding on text styles (paddingBottom: 0.01) keeps the box height fractional, so Yoga's text rounding rounds it up to the next pixel and the float error can't eat the last line.
Activity
react-native-bot commentedon Oct 9, 2026
Warning
Missing reproducer: We could not detect a reproducible example in your issue report. Reproducers are mandatory and we can accept only one of those as a valid reproducer:
You can read more about about it on our website: How to report a bug.
ybelakov commentedon Oct 9, 2026
[Reproducer] Text drops its last line when its box crosses y = 2048 o…
ybelakov commentedon Oct 9, 2026
Reproducer as a pull request editing
RNTesterPlayground.js: #58972 (draft, not for merging). On a 3x iOS simulator the boxes crossing y = 2048 on a fraction show only their first line and log a height of 43.9998779296875.Text: keep the last line when Yoga's pixel-grid rounding leaves the b…
Text: keep the last line when Yoga's pixel-grid rounding leaves the b…
Text: keep the last line when Yoga's pixel-grid rounding leaves the b…
ybelakov commentedon Oct 9, 2026
Fix for the React Native side (works with or without the Yoga fix): #58974. On a React Native built from source with that change, the reproducer draws all twelve boxes with both lines while Yoga still reports the 43.9998779296875pt box, and explicit short heights (30, 43.67) still clip as before.