react-native-vision-camera 導入ガイド

## はじめに
本記事では、未公開の純粋なReact Nativeアプリにカメラによる写真撮影機能を導入しましたので、その紹介記事となります。

## react-native-vision-cameraの導入背景
まずは、カメラライブラリを選定する過程で、最適な選択肢を探るために、ChatGPTを活用しました。このプロセスで、当初「react-native-camera」を推奨されました。

しかしながら、[Github](https://github.com/react-native-camera/react-native-camera)を見てみると、
**react-native-camera is deprecated.(非推奨)**
とありましたので早々に断念しました。また、
Due to the lack of maintainers and increased code complexity, react-native-camera is now deprecated in favor of react-native-vision-camera.（react-native-vision-cameraを支持する）
ともありましたので、react-native-vision-camera を導入するに至りました。

また、他によく使用されるライブラリとして、react-native-image-pickerがあるようです。
こちらは、またの機会に触ってみたいと思います。

このreact-native-vision-cameraですが、[ドキュメント](https://react-native-vision-camera.com/docs/guides)が充実していることも後押しとなりました。ドキュメントを見ればわかりますが、基本的な実装に困ることはないでしょう。


## 環境設定 ##
当方の開発環境とバージョンは以下のとおりです。iPhoneでのみ動作検証しています。
・MacBook Pro M1(Ventura 13.6.3)
・Xcode:15.1
・iOS:17.2
・react-native:0.73.1
・react-native-vision-camera:3.6.17

ドキュメントに沿って進めていけば基本的には問題ないはずですが、
まずはインストールしていきます。
```zsh
npm install react-native-vision-camera
(cd ios/ && pod install)
```
その後、Info.plistにカメラ使用許可の説明を追記します。
```xml
<key>NSCameraUsageDescription</key>
<string>このアプリは写真を撮るためにカメラを使用します</string>
```
これで実装の準備は完了です。


## カメラを起動するコード
続いて、パーミッションの確認とカメラを起動するコードを実装します。
以下のサンプルコードは、カメラ用のコンポーネントとなり、
パーミッションを確認・要求して、カメラを起動するためのシンプルなコードです。
```typescript
import React, { useEffect, useRef } from 'react';
import { View, Text, Button } from 'react-native';
import { Camera, useCameraPermission, useCameraDevice } from 'react-native-vision-camera';

const CameraComponent = ({onClose}) => {
  const { hasPermission, requestPermission } = useCameraPermission(); // カメラの権限を取得
  const device = useCameraDevice('back'); // バックカメラを使用
  const camera = useRef<Camera>(null); // カメラの参照を保持

  useEffect(() => {
    // 初回起動時、権限がない場合に要求
    if (hasPermission === false) {
      requestPermission();
    }
  }, [hasPermission, requestPermission]);

  if (hasPermission === true) {
    if (!device) {
      // デバイスがない場合の表示
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Text>カメラが見つかりませんでした。</Text>
        </View>
      );
    }
    return (
      <Camera
        ref={camera}
        style={{ width: '100%', height: '100%' }}
        device={device}
        isActive={true}
      />
    );
  } else if (hasPermission === false) {
    // 権限を拒否した場合、設定リンクを表示
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>カメラの権限が必要です。</Text>
        <Button title="設定を開く" onPress={() => Linking.openSettings()} />
      </View>
    );
  } else {
    // 権限要求中の表示
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>権限を要求中...</Text>
      </View>
    );
  }
};
```

権限確認のための判定がありますが、大事なのは、\<Camera ... />タグです。
これさえあればカメラは起動できるでしょう。

## 撮影ボタンを配置するコード
続いて、撮影ボタンを配置するコードを実装します。
```typescript
// 撮影ボタン
<View style={styles.buttonContainer}>
  <TouchableOpacity style={styles.button} onPress={takePhoto}>
    <Text style={styles.buttonText}>撮影</Text>
  </TouchableOpacity>
</View>

const styles = StyleSheet.create({
  // 撮影ボタンのコンテナ
  buttonContainer: {
    position: 'absolute',
    bottom: 20,
    alignSelf: 'center',
  },
  // 撮影ボタン
  button: {
    width: 70,
    height: 70,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
    borderRadius: 35,
    opacity: 0.7,
  },
  // 撮影ボタンのテキスト
  buttonText: {
    fontSize: 18,
    color: '#000',
  },
});

```
撮影ボタンは、シンプルに丸いボタンとして、画面の中央下部に配置するようにしました。

## 撮影するコード
撮影ボタンがタップされた時の処理は以下のコードです。

```typescript
const takePhoto = async () => {
  if (camera.current) {
    // 写真を撮影
    const photo = await camera.current.takePhoto();

    // ファイルパスからBlobデータを取得
    const response = await fetch(`file://${photo.path}`);
    const blob = await response.blob();
    // 撮影後の処理 ...
  }
};
```
撮影後のデータは、ファイルパスで扱うも良し、Blobデータとしても扱うも良し、
それぞれのプロジェクト要件によって異なるため、データを扱う処理の記載は割愛します。

## 閉じるボタンを配置するコード
起動したカメラを閉じるためのコードは以下となります。
```typescript
// 閉じるボタン
<View style={styles.closeButtonContainer}>
  <TouchableOpacity style={styles.closeButton} onPress={onClose}>
    <Text style={styles.closeButtonText}>×</Text>
  </TouchableOpacity>
</View>

const styles = StyleSheet.create({
  // 閉じるボタンのコンテナ
  closeButtonContainer: {
    position: 'absolute',
    top: 20,
    left: 20,
    width: 40,
    height: 40,
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 10,
  },
  // 閉じるボタン
  closeButton: {
    width: 40,
    height: 40,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0,0,0,0.6)',
    borderRadius: 20,
  },
  closeButtonText: {
    color: 'white',
    fontSize: 24,
  },
});
```
画面の左上に「×」印の丸いボタンを配置していて、単純ですね。

この閉じるボタンをタップしたときの処理は、親コンポーネントに onClose 関数を渡しています。
onClose を受け取った親コンポーネントは、これを使ってカメラコンポーネントを閉じるためのState関数（例えば setShowCameraModal(false) など）を実行します。
また、撮影ボタンをタップした後、撮影画像の処理が完了し、もしカメラを閉じる必要がある場合にも、同じ onClose 関数やState関数を使用することが可能になります。

## 所感
ここまでカメラを起動し、写真撮影と閉じる処理のサンプルを簡単に記述しました。
公式ガイドには、ズームやビデオ撮影、QRコードの読み込みなどの使用方法も解説されています。
このライブラリは、カスタマイズが自由自在となる点では大変優れたライブラリと言えるでしょう。しかし、要件が増えると処理やコードが煩雑になる可能性がありますので、留意が必要だと感じました。

以上、react-native-vision-cameraの導入ガイドでした。