This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

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

ページを編集

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


この章では、サードパーティのライブラリを使ってスクリーンショットを撮影し、デバイスのメディアライブラリに保存する方法を学びます。スクリーンショットの撮影には react-native-view-shot を、デバイスのメディアライブラリへの画像の保存には expo-media-library を使用します。web ブラウザーはアプリにメディアライブラリへのアクセスを提供しないため、web ではスクリーンショットを画像ファイルとしてダウンロードします。

視聴:ユニバーサル Expo アプリでスクリーンショットを撮影する
視聴:ユニバーサル Expo アプリでスクリーンショットを撮影する

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


1

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

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

Terminal
- npx expo install react-native-view-shot expo-media-library
- yarn expo install react-native-view-shot expo-media-library
- pnpm expo install react-native-view-shot expo-media-library
- bun expo install react-native-view-shot expo-media-library

2

権限を要求する

デバイスのメディアライブラリへのアクセスのような機密性の高い情報を必要とするアプリでは、アクセスを許可または拒否するための権限を要求する必要があります。expo-media-library の usePermissions() フックは、permissionResponse と、アクセスを要求するための requestPermission() メソッドを返します。このアプリはメディアライブラリに画像を保存するだけなので、writeOnly: true オプションを渡して、読み取りアクセスなしで書き込みアクセスを要求します。

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

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

src/app/(tabs)/index.tsx
import { useEffect, useState } from 'react'; import * as MediaLibrary from 'expo-media-library'; // ...rest of the code remains same export default function Index() { const [permissionResponse, requestPermission] = MediaLibrary.usePermissions({ writeOnly: true }); // ...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 が返されます。

前の章で、<ImageViewer> と <EmojiSticker> コンポーネントを <View> で囲みました。この <View> を撮影します。

  1. react-native-view-shot から captureRef を、React から useRef と ComponentRef 型をインポートします。
  2. <View> の参照を保持する imageRef 参照変数を作成します。
  3. <ImageViewer> と <EmojiSticker> コンポーネントを囲む <View> の ref プロパティに、参照変数を渡します。
src/app/(tabs)/index.tsx
import { useEffect, useRef, useState, type ComponentRef } from 'react'; import { captureRef } from 'react-native-view-shot'; export default function Index() { const imageRef = useRef<ComponentRef<typeof View>>(null); // ...rest of the code remains same return ( <SafeAreaView 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 */} </SafeAreaView> ); }

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

4

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

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

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

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

src/app/(tabs)/index.tsx
import { useEffect, useRef, useState, type ComponentRef } from 'react'; import { Platform, View, StyleSheet } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import * as ImagePicker from 'expo-image-picker'; import * as MediaLibrary from 'expo-media-library'; import { captureRef } from 'react-native-view-shot'; import type { ImageSource } from 'expo-image'; import Button from '@/components/button'; import ImageViewer from '@/components/image-viewer'; import IconButton from '@/components/icon-button'; import CircleButton from '@/components/circle-button'; import EmojiPicker from '@/components/emoji-picker'; import EmojiList from '@/components/emoji-list'; import EmojiSticker from '@/components/emoji-sticker'; 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<ImageSource | undefined>(undefined); const [permissionResponse, requestPermission] = MediaLibrary.usePermissions({ writeOnly: true }); const imageRef = useRef<ComponentRef<typeof View>>(null); useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); const pickImageAsync = async () => { const 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.Asset.create(localUri); alert('Saved!'); } catch (e) { console.log(e); } }; return ( <SafeAreaView 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.footerContainer}> <View style={styles.optionsRow}> <IconButton icon={{ android: require('@expo/material-symbols/refresh.xml'), ios: 'arrow.counterclockwise', web: 'refresh', }} label="Reset" onPress={onReset} /> <CircleButton onPress={onAddSticker} /> <IconButton icon={{ android: require('@expo/material-symbols/download.xml'), ios: 'square.and.arrow.down', web: 'download', }} 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> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#25292e', alignItems: 'center', }, imageContainer: { flex: 1, paddingTop: Platform.select({ web: 96, default: 20 }), }, footerContainer: { flex: 1 / 3, alignItems: 'center', }, optionsRow: { alignItems: 'center', flexDirection: 'row', }, });

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

5

web でスクリーンショットを保存する

react-native-view-shot は web でもスクリーンショットを撮影できます。ただし、expo-media-library は Android と iOS でのみ動作します。web では、代わりにスクリーンショットを画像ファイルとしてダウンロードします。

React Native の Platform モジュールを使うと、プラットフォーム固有の動作を実装できます。web では、result オプションを 'data-uri' に設定すると、captureRef() はスクリーンショットを データ URL として返します。このデータ URL をリンク要素に渡すと、画像をダウンロードできます。

src/app/(tabs)/index.tsx の中で、onSaveImageAsync() 関数を更新し、Platform.OS プロパティを使って現在のプラットフォームが 'web' かどうかを判定します。

  1. 'web' でない場合は、Android と iOS 向けに追加したものと同じロジックを引き続き使用します。
  2. 'web' の場合は、captureRef() メソッドを使って現在の <View> を JPEG 画像として撮影します。次に、データ URL を持つリンク要素を作成し、それをクリックして画像をダウンロードします。

imageContainer スタイルで使用しているため、Platform モジュールはすでに react-native からインポートされています。

src/app/(tabs)/index.tsx
import { useEffect, useRef, useState, type ComponentRef } from 'react'; import { Platform, View, StyleSheet } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import * as ImagePicker from 'expo-image-picker'; import * as MediaLibrary from 'expo-media-library'; import { captureRef } from 'react-native-view-shot'; import type { ImageSource } from 'expo-image'; import Button from '@/components/button'; import ImageViewer from '@/components/image-viewer'; import IconButton from '@/components/icon-button'; import CircleButton from '@/components/circle-button'; import EmojiPicker from '@/components/emoji-picker'; import EmojiList from '@/components/emoji-list'; import EmojiSticker from '@/components/emoji-sticker'; 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<ImageSource | undefined>(undefined); const [permissionResponse, requestPermission] = MediaLibrary.usePermissions({ writeOnly: true }); const imageRef = useRef<ComponentRef<typeof View>>(null); useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); const pickImageAsync = async () => { const 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 () => { if (Platform.OS !== 'web') { try { const localUri = await captureRef(imageRef, { height: 440, quality: 1, }); await MediaLibrary.Asset.create(localUri); alert('Saved!'); } catch (e) { console.log(e); } } else { try { const dataUrl = await captureRef(imageRef, { format: 'jpg', quality: 0.95, result: 'data-uri', }); const link = document.createElement('a'); link.download = 'sticker-smash.jpeg'; link.href = dataUrl; link.click(); } catch (e) { console.log(e); } } }; return ( <SafeAreaView 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.footerContainer}> <View style={styles.optionsRow}> <IconButton icon={{ android: require('@expo/material-symbols/refresh.xml'), ios: 'arrow.counterclockwise', web: 'refresh', }} label="Reset" onPress={onReset} /> <CircleButton onPress={onAddSticker} /> <IconButton icon={{ android: require('@expo/material-symbols/download.xml'), ios: 'square.and.arrow.down', web: 'download', }} 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> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#25292e', alignItems: 'center', }, imageContainer: { flex: 1, paddingTop: Platform.select({ web: 96, default: 20 }), }, footerContainer: { flex: 1 / 3, alignItems: 'center', }, optionsRow: { alignItems: 'center', flexDirection: 'row', }, });

web ブラウザーでアプリを実行すると、スクリーンショットを保存できるようになります。

まとめ

Chapter 7: Take a screenshot

We've successfully used react-native-view-shot and expo-media-library to capture a screenshot and save it on the device's library, and to download it on the web.

The app does everything we set out for it to do. In the next chapter, we will customize the app's status bar, splash screen, and app icon.

次へ:Chapter 8: Configure status bar, splash screen and app icon