react / react/react-native

[iOS][Fabric] numberOfLines={1} with CJK text collapses to first character + ellipsis when tail truncation is required (0.86.0)

オープン
#57,688 コメント 3 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Needs: Attention Needs: Repro
主要言語
C++
スター
127k
フォーク
25.3k
平均マージ
1日 23時間
マージ済み PR(30日)
4

説明

Description

On iOS with the New Architecture (RN 0.86.0), <Text numberOfLines={1}> collapses to just the first character + ellipsis (e.g. 中文标题(银色示例、金色文字)中...) when all of the following hold:

  1. the string needs tail truncation (wider than the container), and
  2. the string contains a full-width bracket pair (…), and
  3. the string ends with the closing bracket .

Line wrapping of the same string (no numberOfLines) breaks at exactly the container edge, and middle / head / clip ellipsization all work — only the tail truncation path collapses. Android renders all cases correctly.

Minimal reproducer

No flex involved — a fixed-width box is enough:

import React from 'react';
import { View, Text } from 'react-native';

const CASES = [
  '中文标题(银色示例、金色文字)', // ❌ BUG: renders as 中... (first char + ellipsis)
  '中文标题(银色示例、金色文字)字', // ✅ same + 1 trailing han char: truncates correctly
  '一段较长的中文标题文字内容例)', // ✅ unpaired closing bracket: correct
  '一段较长的中文标题文字内容示例。', // ✅ ends with full-width period: correct
  'Mechanical watch gold (silver)', // ✅ ASCII closing paren: correct
];

export default function App() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <View style={{ width: 240, borderWidth: 1 }}>
        {CASES.map((s, i) => (
          <Text key={i} numberOfLines={1} style={{ fontSize: 17 }}>
            {s}
          </Text>
        ))}
      </View>
    </View>
  );
}

Reproducer: Open in Expo Snack (iOS) — same code as above, loaded via the Snack code URL parameter.

Evidence / trigger characterization

Measured on device via onTextLayout (lines[0].width, container 240pt, fontSize 17):

String Shape lines[0].width Result
中文标题(银色示例、金色文字) bracket pair, ends with 34 ❌ collapses to 中...
中文标题(银色示例、金色文字)字 same + trailing han char 231
一段较长的中文标题文字内容例) at end, no opening 236
一段较长的中文标题文字内容示例。 ends with 236
Mechanical watch gold (silver) ASCII ) at end 231
一段较长的中文标题文字内容示例 pure han, slight overflow 236
long CJK strings overflowing by a lot (with or without brackets mid-string) ~236

Further observations for the failing case:

  • onTextLayout reports a single line whose text is the full string but whose width is ≈ 34pt (first glyph + ellipsis), while onLayout of the <Text> reports the correct frame width (240pt).
  • Without numberOfLines, the same string wraps at exactly the container edge — line breaking is correct; only tail truncation collapses.
  • ellipsizeMode="middle" | "head" | "clip" all render correctly for the same string.
  • Independent of font weight, explicit lineHeight, lineBreakStrategyIOS (none / standard), and layout context (fixed-width box, flex column, flex row all reproduce identically).
  • Android (same JS) renders all cases correctly.

Expected behavior

Fills the available width before ellipsizing, e.g. 中文标题(银色示例、金色... — as Android does.

Actual behavior

Renders 中... (first character + ellipsis) with the rest of the 240pt frame empty.

Workaround

Platform-gate the ellipsize mode for potentially-CJK single-line text: ellipsizeMode={Platform.OS === "ios" ? "middle" : "tail"}.

Version / environment

  • react-native 0.86.0 (New Architecture / Fabric), Hermes
  • Reproduces: physical iPhone on iOS 26.5.2 — both in an Expo SDK 57 development client (RN 0.86.0) and in Expo Go (SDK 57) opening the Snack above
  • Does NOT reproduce: the same Snack in Snack's embedded iOS simulator — but note that the embedded simulator only offers SDK 54 (RN 0.81.x) (SDK 55 is listed but errors out), so this contrast varies both the RN version and the OS version and cannot isolate which one matters
  • Interpretation: the non-repro on the SDK 54 simulator is consistent with a regression introduced somewhere in RN 0.82–0.86 (e.g. the truncation-pipeline changes), but an iOS 26.x TextKit behavior change cannot be excluded either — we have no second iOS device/OS to separate the two
  • Android with the same JS is unaffected

Possibly related

  • #57499 (NSRangeException in processTruncatedAttributedText — same truncation pipeline)
  • #57501 (clipping of last-line orphan token without numberOfLines — possibly the same backtracking logic)

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず、RN 0.86.0 を使用して物理 iOS デバイス上で CJK 括弧ケースを再現し、次に processTruncatedAttributedText と関連する issue #57499 および #57501 を含む、Fabric の末尾切り詰め処理の経路を調査します。失敗する文字列が末尾を省略表示する前に利用可能な幅を埋め、列挙された対照ケースが正しいままであれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
ios, react-native
領域
mobile
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
52/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。