スクリーンショットを撮影する

ページを編集

このチュートリアルでは、サードパーティのライブラリと Expo Media Library を使ってスクリーンショットを撮影する方法を学びます。


For the complete documentation index, see llms.txt. Use this file to discover all available pages.

この章では、サードパーティのライブラリを使ってスクリーンショットを撮影し、デバイスのメディアライブラリに保存する方法を学びます。スクリーンショットの撮影には react-native-view-shot を、デバイスのメディアライブラリへの画像の保存には expo-media-library を使用します。

ここまで、react-native-gesture-handlerreact-native-reanimated などのサードパーティのライブラリを使用してきました。用途に応じて、React Native Directory で他にも数百のサードパーティのライブラリを見つけることができます。
視聴:ユニバーサル Expo アプリでスクリーンショットを撮影する
視聴:ユニバーサル Expo アプリでスクリーンショットを撮影する

react-native-view-shot でスクリーンショットを撮影し、expo-media-library を使ってデバイスのメディアライブラリに保存します。


1

ライブラリをインストールする

react-native-view-shotexpo-media-library をインストールするには、次のコマンドを実行します。

Terminal
npx expo install react-native-view-shot expo-media-library

2

権限を要求する

デバイスのメディアライブラリへのアクセスのような機密性の高い情報を必要とするアプリでは、アクセスを許可または拒否するための権限を要求する必要があります。expo-image-pickeruseMediaLibraryPermissions() フックを使うと、permissionResponserequestPermission() メソッドを利用してアクセスを要求できます。このフックは読み取りと書き込みの両方の権限を要求するため、ライブラリからの画像選択とスクリーンショットの保存の両方に対応します。

アプリが初めて起動され、権限のステータスが許可も拒否もされていない場合、permissionResponse の値は null になります。権限を要求されると、ユーザーは権限を許可するか拒否するかを選択できます。許可されていない場合に判定する条件を追加します。許可されていない場合は、requestPermission() メソッドを呼び出します。アクセスが得られると、permissionResponse の値は granted に変わります。

次のコードスニペットを src/app/(tabs)/index.tsx の中に追加します。

src/app/(tabs)/index.tsx
import { useEffect, useState } from 'react'; import * as ImagePicker from 'expo-image-picker'; // ...rest of the code remains same export default function Index() { const [permissionResponse, requestPermission] = ImagePicker.useMediaLibraryPermissions(); // ...rest of the code remains same useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); // ...rest of the code remains same }

3

現在のビューを保存するための ref を作成する

アプリ内でユーザーがスクリーンショットを撮影できるようにするため、react-native-view-shot を使用します。このライブラリは、captureRef() メソッドを使って <View> のスクリーンショットを画像として撮影します。撮影したスクリーンショット画像ファイルの URI が返されます。

  1. react-native-view-shot から captureRef を、React から useRef をインポートします。
  2. 撮影したスクリーンショット画像の参照を保持する imageRef 参照変数を作成します。
  3. <ImageViewer><EmojiSticker> コンポーネントを <View> で囲み、それに参照変数を渡します。
app/(tabs)/index.tsx
import { useState, useRef } from 'react'; import { captureRef } from 'react-native-view-shot'; export default function Index() { const imageRef = useRef<View>(null); // ...rest of the code remains same return ( <GestureHandlerRootView style={styles.container}> <View style={styles.imageContainer}> <View ref={imageRef} collapsable={false}> <ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} /> {pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />} </View> </View> {/* ...rest of the code remains same */} </GestureHandlerRootView> ); }

上のスニペットでは、collapsable プロパティを false に設定しています。これにより、<View> コンポーネントは背景画像と絵文字ステッカーのみをスクリーンショットの対象にできます。

4

スクリーンショットを撮影して保存する

onSaveImageAsync() 関数の中で react-native-view-shotcaptureRef() メソッドを呼び出すことで、ビューのスクリーンショットを撮影できます。このメソッドは省略可能な引数を受け取り、スクリーンショットを撮影する領域の widthheight を渡せます。利用可能なオプションの詳細については、ライブラリのドキュメント を参照してください。

また、captureRef() メソッドはスクリーンショットの URI で解決される Promise を返します。この URI を MediaLibrary.saveToLibraryAsync() のパラメータとして渡し、デバイスのメディアライブラリにスクリーンショットを保存します。

app/(tabs)/index.tsx の中で、onSaveImageAsync() 関数を次のコードに更新します。

app/(tabs)/index.tsx
import * as ImagePicker from 'expo-image-picker'; import * as MediaLibrary from 'expo-media-library'; import { useEffect, useRef, useState } from 'react'; import { ImageSourcePropType, StyleSheet, View } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { captureRef } from 'react-native-view-shot'; import Button from '@/components/Button'; import CircleButton from '@/components/CircleButton'; import EmojiList from '@/components/EmojiList'; import EmojiPicker from '@/components/EmojiPicker'; import IconButton from '@/components/IconButton'; import ImageViewer from '@/components/ImageViewer'; import EmojiSticker from '@/components/EmojiSticker'; const PlaceholderImage = require('@/assets/images/background-image.png'); export default function Index() { const [selectedImage, setSelectedImage] = useState<string | undefined>( undefined ); const [showAppOptions, setShowAppOptions] = useState<boolean>(false); const [isModalVisible, setIsModalVisible] = useState<boolean>(false); const [pickedEmoji, setPickedEmoji] = useState< ImageSourcePropType | undefined >(undefined); const [permissionResponse, requestPermission] = ImagePicker.useMediaLibraryPermissions(); const imageRef = useRef<View>(null); useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); const pickImageAsync = async () => { let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], allowsEditing: true, quality: 1, }); if (!result.canceled) { setSelectedImage(result.assets[0].uri); setShowAppOptions(true); } else { alert('You did not select any image.'); } }; const onReset = () => { setShowAppOptions(false); }; const onAddSticker = () => { setIsModalVisible(true); }; const onModalClose = () => { setIsModalVisible(false); }; const onSaveImageAsync = async () => { try { const localUri = await captureRef(imageRef, { height: 440, quality: 1, }); await MediaLibrary.saveToLibraryAsync(localUri); if (localUri) { alert('Saved!'); } } catch (e) { console.log(e); } }; return ( <GestureHandlerRootView style={styles.container}> <View style={styles.imageContainer}> <View ref={imageRef} collapsable={false}> <ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} /> {pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />} </View> </View> {showAppOptions ? ( <View style={styles.optionsContainer}> <View style={styles.optionsRow}> <IconButton icon="refresh" label="Reset" onPress={onReset} /> <CircleButton onPress={onAddSticker} /> <IconButton icon="save-alt" label="Save" onPress={onSaveImageAsync} /> </View> </View> ) : ( <View style={styles.footerContainer}> <Button theme="primary" label="Choose a photo" onPress={pickImageAsync} /> <Button label="Use this photo" onPress={() => setShowAppOptions(true)} /> </View> )} <EmojiPicker isVisible={isModalVisible} onClose={onModalClose}> <EmojiList onSelect={setPickedEmoji} onCloseModal={onModalClose} /> </EmojiPicker> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#25292e', alignItems: 'center', }, imageContainer: { flex: 1, }, footerContainer: { flex: 1 / 3, alignItems: 'center', }, optionsContainer: { position: 'absolute', bottom: 80, }, optionsRow: { alignItems: 'center', flexDirection: 'row', }, });

それでは、アプリで写真を選び、ステッカーを追加してから「Save」ボタンをタップしてみましょう。Android と iOS で次のような結果が表示されるはずです。

まとめ

第 7 章:スクリーンショットを撮影する

react-native-view-shotexpo-media-library を使用して、スクリーンショットを撮影しデバイスのライブラリに保存できました。

次の章では、モバイルと web プラットフォームの違いを処理して、web で同じ機能を実装する方法を学びます。

次へ:プラットフォームの違いに対応する