◉RORK LABEN
●EXPO — EAS Observe がネイティブクラッシュも記録(10/07)。SDK 57 は 57.0.21 以降で確認できます●SDK 58 — SDK 58 Beta は 9/15 公開。stable の日付はまだ未確認です●RN 0.88 — React Native 0.88.x の正式リリース予定は 10/12、残り3日●Q&A — expo-widgets が本番ビルドだけ真っ黒になる、という問いが出ています●RORK — 09-29 に GPT-6.1 Sol を追加。Pro・Max プランで利用できます●NEW — クライアントのアプリを作る前に決める、公開アカウントの持ち主●EXPO — EAS Observe がネイティブクラッシュも記録(10/07)。SDK 57 は 57.0.21 以降で確認できます●SDK 58 — SDK 58 Beta は 9/15 公開。stable の日付はまだ未確認です●RN 0.88 — React Native 0.88.x の正式リリース予定は 10/12、残り3日●Q&A — expo-widgets が本番ビルドだけ真っ黒になる、という問いが出ています●RORK — 09-29 に GPT-6.1 Sol を追加。Pro・Max プランで利用できます●NEW — クライアントのアプリを作る前に決める、公開アカウントの持ち主
記事一覧/開発ツール
⬡ 開発ツール/2026-07-04中級

「続きを読む」を出すべきか、テキストの実測で決める — Rork(Expo)の折りたたみ表示設計

商品説明やレビュー本文を3行で畳み「続きを読む」を出す実装で、短い文にまでトグルが付いてしまう問題を解決します。onTextLayout で実際の行数を測り、はみ出す時だけトグルを出す設計を、iOS と Android の差、展開アニメーション、文字サイズ拡大への配慮まで動くコードでまとめます。

Rork577Expo213React Native238テキスト表示個人開発215

✦ プレミアム記事

商品説明の下に「続きを読む」を付けたはずが、たった一行の短い説明にまでトグルが並んでしまう。個人開発で作っていた壁紙アプリの詳細画面で、私自身このちぐはぐさに手が止まったことがあります。長い本文は畳みたい、けれど短い本文にトグルは要らない。この「出す・出さない」を、文字数のような当てにならない目安ではなく、実際に描画された行数で決めたい、というのが今回の話です。

React Native(Rork が生成する標準のアプリはこの土台の上に乗ります)の Text は、numberOfLines を指定すれば末尾を「…」で切り詰めてくれます。ただ、切り詰めが起きたかどうかを教えてはくれません。トグルを正しく出すには、レンダリングされた本文が本当に指定行数を超えているかを、こちら側で測る必要があります。

文字数で判定すると必ずどこかで外れる

最初に思いつくのは「本文が120文字を超えたらトグルを出す」といった閾値です。手軽ですが、これは早晩ずれます。日本語と英語では1行に載る文字数がまるで違いますし、絵文字や URL、改行の入り方でも変わります。同じ100文字でも、横に長い端末では2行に収まり、iPhone の小さな画面では4行になります。文字数は「画面上で何行になるか」をまったく保証しません。

私は一度この閾値方式で出して、日本語では丁度よく見えたのに、英語ロケールに切り替えた瞬間に大量の空振りトグルが出てヒヤリとしました。判定の基準を「入力の長さ」ではなく「描画の結果」に移すのが、遠回りに見えて確実でした。

onTextLayout で「描画された行数」を受け取る

React Native の Text には onTextLayout というコールバックがあります。テキストが実際にレイアウトされたあと、各行の情報を配列で渡してくれます。行数はこの配列の長さで分かります。

肝心なのは、測るときは numberOfLines を付けないことです。付けたまま測ると、レイアウト結果もその行数に丸められてしまい、「本来は5行だが3行に切られた」のか「もともと3行ちょうど」なのかを区別できません。そこで、いったん制限なしで一度だけ測り、その結果を見てトグルの要否を決める、という順番にします。

import { useState, useCallback } from 'react';
import { Text, Pressable, View, type TextLayoutEventData, type NativeSyntheticEvent } from 'react-native';
 
const COLLAPSED_LINES = 3;
 
type Props = { children: string };
 
export function ExpandableText({ children }: Props) {
  // needsToggle: 本文が3行を超えるか(=トグルを出すか)
  // measured: 一度だけ測り終えたか
  const [needsToggle, setNeedsToggle] = useState(false);
  const [measured, setMeasured] = useState(false);
  const [expanded, setExpanded] = useState(false);
 
  const onTextLayout = useCallback(
    (e: NativeSyntheticEvent<TextLayoutEventData>) => {
      if (measured) return; // 展開後の再レイアウトで上書きしない
      const lineCount = e.nativeEvent.lines.length;
      setNeedsToggle(lineCount > COLLAPSED_LINES);
      setMeasured(true);
    },
    [measured],
  );
 
  return (
    <View>
      {/* 測定パス: 画面外に制限なしで一度だけ描画して行数を得る */}
      {!measured && (
        <Text
          onTextLayout={onTextLayout}
          style={{ position: 'absolute', opacity: 0, left: 0, right: 0 }}
          accessibilityElementsHidden
          importantForAccessibility="no-hide-descendants"
        >
          {children}
        </Text>
      )}
 
      {/* 表示パス: 折りたたみ時だけ numberOfLines を効かせる */}
      <Text numberOfLines={expanded ? undefined : COLLAPSED_LINES}>
        {children}
      </Text>
 
      {needsToggle && (
        <Pressable
          onPress={() => setExpanded((v) => !v)}
          hitSlop={8}
          accessibilityRole="button"
          accessibilityLabel={expanded ? '本文を折りたたむ' : '本文の続きを読む'}
        >
          <Text style={{ color: '#2563eb', marginTop: 4 }}>
            {expanded ? '閉じる' : '続きを読む'}
          </Text>
        </Pressable>
      )}
    </View>
  );
}

測定用の Text を絶対配置+透明で画面外に一度だけ置き、行数を得たら消す。表示用の Text は折りたたみ時のみ numberOfLines を効かせる。この二枚重ねが、切り詰めの有無を正しく知りつつ、ちらつきを最小に抑える形でした。測定用要素はスクリーンリーダーから隠す指定を必ず添えます。読み上げが本文を二重に拾わないためです。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦onTextLayout で描画後の実際の行数を測り、本文が3行を超える時だけ「続きを読む」を出す判定を、動くコードで実装できるようになります
✦numberOfLines を付けたまま測ると常に3行に丸められてしまう罠を避け、iOS と Android で測り方が食い違う理由と回避策が分かります
✦展開・折りたたみの LayoutAnimation が Android で無反応になる原因と、VoiceOver・文字サイズ200%の利用者に配慮したトグル設計の判断軸が身につきます
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ 開発ツール2026-09-04
EAS の secret は「アプリに入れない」設定ではありません — 接頭辞と可視性を別々に決める
EXPO_PUBLIC_ の接頭辞は成果物に入るかを決め、EAS の可視性は誰が読めるかを決めます。二つを重ねたときに OTA 更新でだけ値が消える筋道と、出荷前に自分の手で確かめる手順をまとめました。
⬡ 開発ツール2026-09-03
リンクを押してもアプリが開かないとき、疑うのは assetlinks.json の置き場所より先に署名鍵です
Android App Links が検証されずブラウザに吸われる原因の多くは指紋の食い違いです。keytool の実出力と、手元で照合できる小さな検査スクリプトの実行結果を添えて整理しました。
⬡ 開発ツール2026-09-01
expo-paste-input で画像を貼り付けられる入力欄を作り、消える file:// を documents へ逃がす
チャット入力に画像やGIFを貼り付けられるようにする expo-paste-input の実装手順です。onPaste が返す file:// は一時ファイルで、送信までの間に消えることがあります。保存先を移す実装と、実機で確かめる順番をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます