Skip to content

Fix for iOS text truncation issues. (0.81.4 branch) - #21

Open
andrewkunkel wants to merge 4 commits into
0.81.4-discordfrom
akunkel/ios-truncation-814
Open

Fix for iOS text truncation issues. (0.81.4 branch)#21
andrewkunkel wants to merge 4 commits into
0.81.4-discordfrom
akunkel/ios-truncation-814

Conversation

@andrewkunkel

@andrewkunkel andrewkunkel commented Jul 23, 2026

Copy link
Copy Markdown

See #19 for the identical fix on 0.86.0. I'm sending this one out first because it's easier for me to test, since 0.86.0 isn't live yet, and I'll send out the same change to that branch afterwards. Actually releasing this to the mobile client can wait until 0.86.0 goes out.

Summary

Display names with a stroke effect (Neon, Toon, Pop) don't truncate correctly on iOS. When the name overflows, instead of showing an ellipsis the last word is silently dropped — Android truncates with "…" as intended, iOS does not. This PR makes iOS match Android.

What broke and why

Layout-impacting effects are clamped to a single line with tail truncation (lineClamp: 1, ellipsizeMode: 'tail' in UsernameWithEffects.tsx). On iOS, stroked text is not drawn by NSLayoutManager — it's drawn by a custom two-pass Core Text renderer (stroke outline + fill) so we can produce a rounded outline of a specific width. That path exists in both the Fabric renderer (RCTTextLayoutManager.mmdrawAttributedString, the path we actually hit) and the legacy one (RCTTextView.mmdrawRect).

The problem: CTFramesetterCreateFrame does its own line breaking and ignores the NSTextContainer's maximumNumberOfLines and truncating line-break mode. So while NSLayoutManager correctly truncates the name to one line with an ellipsis, the Core Text pass is handed the full attributed string, wraps the overflowing last word onto a second line that falls outside the clamped one-line frame, and clips it away — no ellipsis, just a missing word. The non-stroke path never had this bug because it draws the glyphs NSLayoutManager already laid out and truncated.

How we're fixing it

We reconstruct the exact visible, truncated text that NSLayoutManager produced and feed that to Core Text, so the stroke passes render the same content the non-stroke path would.

A shared helper RCTTruncatedAttributedStringForStroke walks the laid-out line fragments and rebuilds the visible string: the visible characters plus an ellipsis carrying the last visible character's attributes, with explicit newlines inserted at soft-wrap points so Core Text reproduces the same line breaks.

Both the Fabric and legacy stroke-drawing paths use this helper for their stroke and fill passes, so overflowing stroked names now show a trailing "…" identical to Android and to non-stroked text.

Before (Android -> iOS)

image

After (Android -> iOS)

image

andrewkunkel and others added 2 commits July 22, 2026 18:47
Extract the duplicated RCTTruncatedAttributedStringForStroke helper out of
RCTTextView.mm and RCTTextLayoutManager.mm into a single shared unit in
React-Core (React/Base/RCTTextStroke.{h,mm}), which both the legacy
(React-RCTText) and Fabric (React-FabricComponents) text pods depend on.

Also document that the helper only supports tail truncation (the mode used
by stroke effects); clip returns the string unchanged and head/middle would
need mode-specific ellipsis placement.

Co-authored-by: Cursor <cursoragent@cursor.com>
@andrewkunkel
andrewkunkel requested a review from insacc July 23, 2026 22:42
@andrewkunkel
andrewkunkel marked this pull request as ready for review July 24, 2026 01:59
@andrewkunkel
andrewkunkel requested a review from Flewp July 24, 2026 01:59

#import <React/RCTTextStroke.h>

NSAttributedString *RCTTruncatedAttributedStringForStroke(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so basically we're adding one additional rendering cycle to get the truncated text and then use it, iiuc, right?

@andrewkunkel andrewkunkel Jul 24, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's my understanding of it. Updated the PR though with a reduced change set that only targets Fabric.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants