dataDetectorTypesだけじゃない!React Nativeでのデータ検出代替手法

2025-06-06

具体的には、以下のようなデータの種類を検出できます。

  • 'all': 上記すべての種類のデータを検出します。
  • 'none': どの種類のデータも検出しません。これがデフォルトの動作です。
  • 'calendarEvent': 日付や時刻を含むテキストからカレンダーイベントを検出します。タップすると、カレンダーにイベントを追加するオプションが表示されます。
  • 'address': 住所を検出します。タップすると、マップアプリでその住所を表示するオプションが表示されます。
  • 'link': URL(ウェブサイトのアドレス)を検出します。タップすると、ウェブブラウザでそのURLを開くオプションが表示されます。
  • 'phoneNumber': 電話番号を検出します。タップすると、電話をかけるためのオプション(電話アプリを開くなど)が表示されます。

使用例

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

const App = () => {
  return (
    <Text style={styles.text} dataDetectorTypes="all">
      電話番号: 090-1234-5678, ウェブサイト: https://www.example.com, 住所: 東京都渋谷区
    </Text>
  );
};

const styles = StyleSheet.create({
  text: {
    fontSize: 18,
    margin: 20,
  },
});

export default App;

この例では、dataDetectorTypes="all"を設定しているため、テキスト内の電話番号、URL、住所がそれぞれタップ可能なリンクとして表示されます。ユーザーがこれらのリンクをタップすると、対応するネイティブ機能(電話アプリ、ブラウザ、マップアプリなど)が起動します。

  • 検出されるデータの精度は、OSのネイティブ機能に依存します。
  • このプロパティは、テキストを表示するだけのTextコンポーネントに適用されます。ユーザーがテキストを入力するためのTextInputコンポーネントには、通常、別途dataDetectorTypesプロパティが用意されており、動作が異なる場合があります(例えば、TextInputの場合はeditable={false}でなければ機能しない、など)。
  • dataDetectorTypesは、基本的にiOSプラットフォームに特化した機能です。Androidでは、ネイティブのTextInputコンポーネントが同様の機能を持つ場合がありますが、React NativeのTextコンポーネントでは、iOSほど柔軟ではありません。


Androidでの動作の制限

問題
dataDetectorTypesは、iOSプラットフォームで最も効果的に機能します。Androidでは、同じように動作しない、あるいは全く機能しない場合があります。特に、特定の種類のデータ(例:'address''calendarEvent')の検出が期待通りに機能しないことがあります。

原因
これは、React NativeがiOSとAndroidのネイティブなデータ検出機能を活用しているためです。AndroidのTextビューは、iOSのUITextViewほど柔軟なデータ検出機能を持たない場合があります。

トラブルシューティング

  • テスト
    常に両方のプラットフォームでテストを行い、期待通りの動作をしているか確認することが重要です。
  • プラットフォームごとの対応
    Androidでも同様の機能を実現したい場合は、別のライブラリを使用するか、自分で正規表現などを使ってデータを検出し、onPressハンドラを持つカスタムコンポーネントを作成する必要があります。例えば、電話番号やURLであれば、Linking APIと正規表現を組み合わせて、手動でタップ可能な要素を作成できます。

TextInputとの混同

問題
TextコンポーネとTextInputコンポーネントのdataDetectorTypesの動作を混同してしまうことがあります。TextInputはユーザー入力のためのコンポーネントであり、dataDetectorTypesの動作もTextとは異なります。例えば、TextInputeditable={true}の場合、dataDetectorTypesは機能しないことがあります。

原因
両者は目的が異なるため、プロパティの動作も異なります。Textは表示に特化していますが、TextInputは入力と編集に焦点を当てています。

トラブルシューティング

  • 目的の明確化
    テキストを表示して自動検出させたいのか、それともユーザーに入力させつつ特定のデータタイプを検出させたいのか、目的を明確にしましょう。後者の場合は、TextInputのプロパティを適切に設定するか、カスタムロジックを検討する必要があります。
  • ドキュメントの確認
    使用しているコンポーネント(TextTextInputか)の公式ドキュメントで、dataDetectorTypesの動作を正確に確認してください。

データ検出の精度と誤検出

問題
dataDetectorTypesが期待通りのデータを検出しない、あるいは誤ったデータを検出することがあります。例えば、単なる数字の羅列が電話番号として検出されたり、短い文字列がURLとして検出されたりすることがあります。

原因
OSのネイティブなデータ検出アルゴリズムに依存するため、完全に正確な検出は保証されません。文脈によって検出精度が変わることがあります。

トラブルシューティング

  • 代替手段の検討
    非常に厳密な検出が必要な場合は、自前で正規表現などを用いてデータを抽出し、カスタムコンポーネントで表示することを検討してください。
  • テキストの整形
    検出させたいデータが明確になるように、表示するテキストのフォーマットを工夫する。例えば、電話番号の前に「電話番号: 」と明示的に書くなど。
  • 特定のタイプに絞る
    dataDetectorTypes="all"ではなく、必要なタイプ(例:dataDetectorTypes="phoneNumber")に絞って設定することで、誤検出を減らせる場合があります。

スタイルとの競合

問題
dataDetectorTypesによって生成されたリンクの色や下線などのスタイルが、アプリケーション全体のスタイルと合わないことがあります。

原因
検出されたデータはネイティブな要素として描画されるため、通常のTextスタイルの適用が限定的になる場合があります。

トラブルシューティング

  • カスタムコンポーネント
    スタイルを完全に制御したい場合は、dataDetectorTypesを使用せず、自分でonPressハンドラを持つTextコンポーネントやTouchableOpacityを組み合わせて、リンクを再現することを検討します。
  • プラットフォーム固有のスタイル調整
    iOSでは、TextコンポーネントのlinkTextAttributes(ただし、これはTextには直接適用されず、内部的に使用されるネイティブ要素のスタイル設定に関連する場合があります)や、tintColorなど、ネイティブ要素の見た目を変更するプロパティがないか確認します。

問題
検出されたデータをタップしても、電話アプリやブラウザアプリが起動しない、またはエラーが発生する場合があります。

原因

  • 無効なデータ
    検出されたデータが、実は無効な電話番号やURLである。
  • アプリの欠如
    ユーザーのデバイスに、対応するアプリ(例:マップアプリ、カレンダーアプリ)がインストールされていない。
  • 権限不足
    例えば電話番号を検出した場合、電話をかけるための権限がアプリに付与されていない。
  • エラーハンドリング
    ユーザーにエラーメッセージを表示したり、代替手段を提供したりするためのエラーハンドリングを検討します。Linking APIなどを自分で使用する場合は、canOpenURLなどで事前に開けるかどうかを確認できます。
  • 有効なデータの使用
    テストデータが実際に有効な形式であるか確認します。
  • 権限の確認
    アプリが必要な権限(例:android.permission.CALL_PHONEなど)をAndroidManifest.xmlInfo.plistで宣言しているか確認します。必要に応じて、実行時に権限を要求するロジックを実装します。


基本的な使用例 (all タイプ)

最も基本的な使用例で、すべてのデータタイプを検出します。

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

const BasicUsageScreen = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>基本的なデータ検出</Text>
      <Text style={styles.text}>
        電話番号: 090-1234-5678, {'\n'}
        ウェブサイト: https://www.google.com, {'\n'}
        住所: 東京都渋谷区渋谷1-1-1, {'\n'}
        イベント: 明日2025年6月7日 午前10時の会議
      </Text>
      {/*
        iOSの場合、dataDetectorTypes="all" は上記全てを検出します。
        Androidでは、電話番号とリンクの検出は比較的安定していますが、
        住所やカレンダーイベントはサポートされないことがあります。
      */}
      <Text style={[styles.text, styles.detectedText]} dataDetectorTypes="all">
        電話番号: 090-1234-5678, {'\n'}
        ウェブサイト: https://www.google.com, {'\n'}
        住所: 東京都渋谷区渋谷1-1-1, {'\n'}
        イベント: 明日2025年6月7日 午前10時の会議
      </Text>
      <Text style={styles.note}>
        (iOSでは、電話番号、リンク、住所、カレンダーイベントが検出され、タップ可能になります。{'\n'}
        Androidでは、検出されるタイプに制限がある場合があります。)
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    color: '#333',
  },
  text: {
    fontSize: 16,
    lineHeight: 24,
    textAlign: 'center',
    marginBottom: 20,
    color: '#555',
  },
  detectedText: {
    backgroundColor: '#e0ffe0', // 検出されたテキストが分かりやすいように背景色を変える
    padding: 10,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#a0ffa0',
  },
  note: {
    fontSize: 12,
    color: '#888',
    marginTop: 20,
    textAlign: 'center',
  },
});

export default BasicUsageScreen;

解説

  • Androidでは、allを指定しても、iOSほど全てのタイプがサポートされるわけではありません。特に住所やカレンダーイベントは、ネイティブの機能に依存するため、動作しないことが多いです。
  • ユーザーが検出された要素をタップすると、iOSでは適切なネイティブアプリ(電話、Safari、マップ、カレンダー)が起動します。
  • dataDetectorTypes="all"を設定することで、React Nativeはテキスト内の電話番号、URL、住所、カレンダーイベントを自動的に検出し、それぞれタップ可能な要素に変換します。

特定のデータタイプのみを検出する例

必要なデータタイプだけを検出するように指定できます。

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

const SpecificDetectionScreen = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>特定のデータ検出</Text>

      {/* 電話番号のみ検出 */}
      <Text style={styles.text}>
        電話番号のみ検出: <Text style={styles.detectedText} dataDetectorTypes="phoneNumber">012-3456-7890</Text>
      </Text>

      {/* URLのみ検出 */}
      <Text style={styles.text}>
        URLのみ検出: <Text style={styles.detectedText} dataDetectorTypes="link">https://github.com/facebook/react-native</Text>
      </Text>

      {/* 住所のみ検出 (iOS向け) */}
      <Text style={styles.text}>
        住所のみ検出 (iOS向け): <Text style={styles.detectedText} dataDetectorTypes="address">大阪府大阪市北区梅田1丁目</Text>
      </Text>

      {/* カレンダーイベントのみ検出 (iOS向け) */}
      <Text style={styles.text}>
        イベントのみ検出 (iOS向け): <Text style={styles.detectedText} dataDetectorTypes="calendarEvent">今日の午後3時に打ち合わせ</Text>
      </Text>

      <Text style={styles.note}>
        (Androidでは、'address'や'calendarEvent'は機能しない場合があります。{'\n'}
        特定のタイプに絞ることで、誤検出を減らすことができます。)
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    color: '#333',
  },
  text: {
    fontSize: 16,
    lineHeight: 24,
    textAlign: 'center',
    marginBottom: 15,
    color: '#555',
  },
  detectedText: {
    backgroundColor: '#e0ffe0',
    paddingHorizontal: 5,
    borderRadius: 4,
    borderWidth: 1,
    borderColor: '#a0ffa0',
  },
  note: {
    fontSize: 12,
    color: '#888',
    marginTop: 20,
    textAlign: 'center',
  },
});

export default SpecificDetectionScreen;

解説

  • これにより、不要な検出を防ぎ、より正確な動作を期待できます。
  • 例では、"phoneNumber", "link", "address", "calendarEvent"を個別に指定しています。
  • dataDetectorTypesプロパティに、文字列で検出したいデータタイプを指定します。

dataDetectorTypesはiOSに特化しているため、Androidでも同様の機能を実現したい場合は、自前でonPressハンドラを使ってリンクを作成することが一般的です。ここでは、電話番号とURLの例を示します。

import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Linking, Platform, Alert } from 'react-native';

const CustomDetectionScreen = () => {
  const phoneNumber = '080-9876-5432';
  const websiteUrl = 'https://www.reactnative.dev/';

  const handlePhonePress = () => {
    Linking.openURL(`tel:${phoneNumber}`).catch(err => {
      console.error('電話をかけられませんでした:', err);
      Alert.alert('エラー', '電話アプリを開けませんでした。');
    });
  };

  const handleLinkPress = () => {
    Linking.openURL(websiteUrl).catch(err => {
      console.error('リンクを開けませんでした:', err);
      Alert.alert('エラー', 'ブラウザを開けませんでした。');
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>カスタムデータ検出(Androidでの代替案)</Text>

      {/* iOS向け(dataDetectorTypesを使用) */}
      {Platform.OS === 'ios' && (
        <View style={styles.platformSection}>
          <Text style={styles.sectionTitle}>iOSの場合 (dataDetectorTypesを使用)</Text>
          <Text style={[styles.text, styles.detectedText]} dataDetectorTypes="all">
            電話番号: {phoneNumber}, {'\n'}
            ウェブサイト: {websiteUrl}
          </Text>
        </View>
      )}

      {/* Android/汎用的な方法(onPressとLinkingを使用) */}
      <View style={styles.platformSection}>
        <Text style={styles.sectionTitle}>Android / 汎用的な場合 (onPressを使用)</Text>
        <Text style={styles.text}>
          電話番号: {' '}
          <TouchableOpacity onPress={handlePhonePress}>
            <Text style={styles.customLink}>{phoneNumber}</Text>
          </TouchableOpacity>
        </Text>
        <Text style={styles.text}>
          ウェブサイト: {' '}
          <TouchableOpacity onPress={handleLinkPress}>
            <Text style={styles.customLink}>{websiteUrl}</Text>
          </TouchableOpacity>
        </Text>
      </View>

      <Text style={styles.note}>
        (Androidでは`dataDetectorTypes`の動作が限定的なため、{'\n'}
        `onPress`と`Linking` APIを組み合わせるのが一般的です。)
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    color: '#333',
  },
  platformSection: {
    width: '100%',
    marginBottom: 30,
    alignItems: 'center',
    borderBottomWidth: 1,
    borderBottomColor: '#ddd',
    paddingBottom: 20,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 15,
    color: '#444',
  },
  text: {
    fontSize: 16,
    lineHeight: 24,
    textAlign: 'center',
    marginBottom: 10,
    color: '#555',
  },
  detectedText: {
    backgroundColor: '#e0ffe0',
    padding: 10,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#a0ffa0',
  },
  customLink: {
    color: '#007bff', // 青色のリンク
    textDecorationLine: 'underline', // 下線
    fontWeight: 'bold',
  },
  note: {
    fontSize: 12,
    color: '#888',
    marginTop: 20,
    textAlign: 'center',
  },
});

export default CustomDetectionScreen;
  • エラーハンドリング(catchブロック)を追加することで、URLが開けなかった場合のユーザー体験を向上させることができます。
  • Linking.openURL('tel:1234567890')は電話アプリを、Linking.openURL('https://example.com')はブラウザアプリを開きます。
  • Androidや、より詳細な制御が必要な場合は、TextコンポーネントをTouchableOpacityで囲み、そのonPressハンドラ内でLinking.openURL()を使用します。
  • Platform.OS === 'ios'を使って、iOSとAndroidで異なるUIやロジックを提供しています。


正規表現とLinking APIを組み合わせる

これは最も一般的で柔軟な代替手段です。テキストコンテンツを正規表現でスキャンし、検出されたパターンに基づいて個別のタップ可能な要素を作成します。

メリット

  • アクションの多様性
    Linking API以外にも、カスタムのモーダルを表示したり、ナビゲーションを行ったりと、さまざまなアクションを実行できます。
  • スタイルの自由度
    検出された要素のスタイルを自由に設定できます。
  • 高いカスタマイズ性
    検出するパターンを完全に制御できます。
  • クロスプラットフォーム
    Android と iOS の両方で一貫した動作を実現できます。

デメリット

  • パフォーマンス
    大量のテキストに対して複雑な正規表現を使用すると、パフォーマンスに影響が出る可能性があります。
  • 実装の複雑さ
    正規表現の記述や、テキストの分割ロジックを自分で実装する必要があります。

実装例(電話番号とURLの検出)

import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Linking, Alert } from 'react-native';

const detectLinks = (text) => {
  const parts = [];
  let lastIndex = 0;

  // 電話番号の正規表現 (例: 000-0000-0000, +81-...)
  const phoneNumberRegex = /(\+?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,4})/g;
  // URLの正規表現 (簡易版)
  const urlRegex = /(https?:\/\/[^\s]+)/g;

  // URLの検出
  text.replace(urlRegex, (match, url, offset) => {
    if (offset > lastIndex) {
      parts.push({ type: 'text', content: text.substring(lastIndex, offset) });
    }
    parts.push({ type: 'link', content: url, url: url });
    lastIndex = offset + match.length;
    return match;
  });

  // 電話番号の検出 (URL検出後に残ったテキストに対して行うことで重複を避ける)
  const remainingText = text.substring(lastIndex);
  lastIndex = 0; // remainingText内でのインデックスをリセット
  
  remainingText.replace(phoneNumberRegex, (match, number, offset) => {
    if (offset > lastIndex) {
      parts.push({ type: 'text', content: remainingText.substring(lastIndex, offset) });
    }
    parts.push({ type: 'phoneNumber', content: number, number: number });
    lastIndex = offset + match.length;
    return match;
  });

  if (lastIndex < remainingText.length) {
    parts.push({ type: 'text', content: remainingText.substring(lastIndex) });
  }

  // 元のtextの全体にわたる処理に修正
  // 注意: 複数の正規表現が重なる場合の処理順序を考慮する必要があります。
  // より堅牢にするには、一度にすべてのパターンを検出して、開始/終了インデックスでソートし、重複を排除するロジックが必要です。
  // ここではシンプルにするため、URLを先に検出して、残りの部分で電話番号を検出しています。
  
  // 例示のため、簡易的なロジックにしています。
  // 実際のプロダクションコードでは、もっと複雑なパーサーを検討してください。
  const finalParts = [];
  let currentIndex = 0;
  
  const allMatches = [];
  let match;

  while ((match = urlRegex.exec(text)) !== null) {
    allMatches.push({ type: 'link', content: match[0], value: match[0], start: match.index, end: match.index + match[0].length });
  }
  urlRegex.lastIndex = 0; // reset regex for next use

  while ((match = phoneNumberRegex.exec(text)) !== null) {
    allMatches.push({ type: 'phoneNumber', content: match[0], value: match[0], start: match.index, end: match.index + match[0].length });
  }
  phoneNumberRegex.lastIndex = 0; // reset regex for next use

  allMatches.sort((a, b) => a.start - b.start);

  for (const item of allMatches) {
    if (item.start > currentIndex) {
      finalParts.push({ type: 'text', content: text.substring(currentIndex, item.start) });
    }
    finalParts.push(item);
    currentIndex = Math.max(currentIndex, item.end);
  }

  if (currentIndex < text.length) {
    finalParts.push({ type: 'text', content: text.substring(currentIndex) });
  }

  return finalParts.map((part, index) => {
    if (part.type === 'link') {
      const handlePress = async () => {
        const supported = await Linking.canOpenURL(part.value);
        if (supported) {
          await Linking.openURL(part.value);
        } else {
          Alert.alert('エラー', `このリンクを開けません: ${part.value}`);
        }
      };
      return (
        <TouchableOpacity key={index} onPress={handlePress}>
          <Text style={styles.linkText}>{part.content}</Text>
        </TouchableOpacity>
      );
    } else if (part.type === 'phoneNumber') {
      const handlePress = async () => {
        const url = `tel:${part.value.replace(/[-.\s]/g, '')}`; // 電話番号からハイフンなどを除去
        const supported = await Linking.canOpenURL(url);
        if (supported) {
          await Linking.openURL(url);
        } else {
          Alert.alert('エラー', `この電話番号に電話できません: ${part.value}`);
        }
      };
      return (
        <TouchableOpacity key={index} onPress={handlePress}>
          <Text style={styles.phoneText}>{part.content}</Text>
        </TouchableOpacity>
      );
    } else {
      return <Text key={index}>{part.content}</Text>;
    }
  });
};

const CustomDetectorScreen = () => {
  const sampleText = "私のウェブサイトは https://www.example.com です。ご連絡は 090-1234-5678 までどうぞ。または [email protected] にメールください。もう一つは www.reactnative.dev です。";

  return (
    <View style={styles.container}>
      <Text style={styles.title}>正規表現とLinking APIによる代替</Text>
      <View style={styles.textContainer}>
        {detectLinks(sampleText)}
      </View>
      <Text style={styles.note}>
        (より複雑なロジックでテキストを解析し、{'\n'}
        必要な部分にカスタムの`onPress`ハンドラを設定します。)
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
    textAlign: 'center',
  },
  textContainer: {
    flexDirection: 'row', // インライン要素として表示
    flexWrap: 'wrap',     // 折り返しを可能にする
    marginBottom: 20,
    backgroundColor: '#fff',
    padding: 15,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#ddd',
  },
  linkText: {
    color: '#007bff',
    textDecorationLine: 'underline',
  },
  phoneText: {
    color: '#28a745', // 緑色の電話番号
    textDecorationLine: 'underline',
  },
  note: {
    fontSize: 12,
    color: '#888',
    marginTop: 20,
    textAlign: 'center',
  },
});

export default CustomDetectorScreen;

ポイント

  • Linking.openURL()を使用して、検出されたデータに対応するネイティブアプリ(ブラウザ、電話)を開きます。Linking.canOpenURL()で事前に開けるかどうかのチェックも重要です。
  • それぞれの部分に対して、適切なTextコンポーネント(リンクや電話番号の場合はTouchableOpacityでラップされたText)をレンダリングします。
  • detectLinks関数は、入力されたテキストを正規表現でスキャンし、通常のテキスト部分と検出されたリンク/電話番号部分に分割します。

より複雑なテキスト解析やリッチテキスト表示が必要な場合、既存のサードパーティライブラリを利用することも検討できます。


  • react-native-parsed-text: テキスト内の特定のパターン(正規表現、特定のキーワードなど)を検出し、それらにカスタムスタイルやonPressハンドラを適用できる、より汎用的なライブラリです。
  • react-native-hyperlink: テキスト内のURL、メールアドレスなどを自動的に検出してリンクに変換してくれるライブラリです。正規表現を自分で書く手間を省けます。

メリット

  • 堅牢性
    多くのエッジケースが考慮されている場合があります。
  • 開発効率の向上
    自分で正規表現やレンダリングロジックを書く手間が省けます。

デメリット

  • メンテナンス
    ライブラリの更新状況やコミュニティのサポートに依存します。
  • カスタマイズの制限
    ライブラリが提供する機能の範囲内でしかカスタマイズできません。
  • 依存関係の追加
    プロジェクトに新たな依存関係が追加されます。

react-native-parsed-text の使用例

まず、ライブラリをインストールします。 npm install react-native-parsed-text または yarn add react-native-parsed-text

import React from 'react';
import { StyleSheet, View, Alert, Linking } from 'react-native';
import ParsedText from 'react-native-parsed-text';

const ParsedTextScreen = () => {
  const handleUrlPress = (url) => {
    Linking.openURL(url).catch(err => Alert.alert('エラー', `このリンクを開けませんでした: ${url}`));
  };

  const handlePhonePress = (number) => {
    Linking.openURL(`tel:${number}`).catch(err => Alert.alert('エラー', `この電話番号に電話できません: ${number}`));
  };

  const handleEmailPress = (email) => {
    Linking.openURL(`mailto:${email}`).catch(err => Alert.alert('エラー', `このメールアドレスにメールできません: ${email}`));
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>ParsedTextライブラリの利用</Text>
      <View style={styles.textContainer}>
        <ParsedText
          style={styles.text}
          parse={[
            { type: 'url', style: styles.linkText, onPress: handleUrlPress },
            { type: 'phone', style: styles.phoneText, onPress: handlePhonePress },
            { type: 'email', style: styles.emailText, onPress: handleEmailPress },
            { pattern: /#(\w+)/, style: styles.hashtagText, onPress: (hashtag) => Alert.alert('ハッシュタグ', `タップされたハッシュタグ: ${hashtag}`) },
            { pattern: /@(\w+)/, style: styles.mentionText, onPress: (mention) => Alert.alert('メンション', `タップされたメンション: ${mention}`) },
          ]}
        >
          これは私のウェブサイトです: https://www.example.com です。
          電話は 090-1111-2222 または 03-3333-4444。
          メールは [email protected] まで。
          今日のイベントは #React Native の勉強会です。
          @JohnDoe さん、お待ちしております!
        </ParsedText>
      </View>
      <Text style={styles.note}>
        (ParsedTextは、内部的に正規表現を使用し、{'\n'}
        より簡単にデータ検出とアクションの設定ができます。)
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
    textAlign: 'center',
  },
  textContainer: {
    marginBottom: 20,
    backgroundColor: '#fff',
    padding: 15,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#ddd',
  },
  text: {
    fontSize: 16,
    lineHeight: 24,
    color: '#555',
  },
  linkText: {
    color: '#007bff',
    textDecorationLine: 'underline',
  },
  phoneText: {
    color: '#28a745',
    textDecorationLine: 'underline',
  },
  emailText: {
    color: '#dc3545', // 赤色のメールアドレス
    textDecorationLine: 'underline',
  },
  hashtagText: {
    color: '#6f42c1', // 紫色のハッシュタグ
    fontWeight: 'bold',
  },
  mentionText: {
    color: '#fd7e14', // オレンジ色のメンション
    fontWeight: 'bold',
  },
  note: {
    fontSize: 12,
    color: '#888',
    marginTop: 20,
    textAlign: 'center',
  },
});

export default ParsedTextScreen;

ポイント

  • これにより、複雑なロジックを自分で書くことなく、様々な種類のデータを検出し、アクションを定義できます。
  • ParsedTextコンポーネントのparseプロパティに、検出したいタイプ('url', 'phone', 'email')やカスタムの正規表現(pattern)と、それらに適用するスタイルやonPressハンドラを配列で渡します。

Text#dataDetectorTypesはiOSで手軽にデータ検出を行うための機能ですが、Androidでの互換性や、より高度なカスタマイズが必要な場合は、上記のような代替手段を検討する必要があります。

  • 自分で正規表現を書く手間を省き、かつ高度なテキスト解析機能(ハッシュタグ、メンションなど)も必要とする場合
    react-native-parsed-textのようなサードパーティライブラリ。
  • Androidを含むクロスプラットフォームで一貫した動作が必要で、スタイルやアクションを細かく制御したい場合
    正規表現とLinking APIを組み合わせた自作ロジック。
  • 簡単な電話番号/URL検出で、iOSのネイティブ動作で十分な場合
    dataDetectorTypesが最適。