본문 바로가기
Front-End/React + Native

[React-Native]앱에서 권한 요청 후 카메라 앱 띄우기

by MS_developer 2024. 4. 20.

 

앱을 개발하다 보면 카메라 앱을 통해 사진이나 동영상 촬영 기능을 필요로 하는 경우가 있다.

 

이때, 카메라 앱 구동에 앞서 해당 앱에 대한 권한 설정이 필요하고, 권한이 설정되었을 때 카메라 앱을 구동시켜야 한다.

 


앱 권한 설정하기

 

앞서 언급한 대로 앱에서 카메라 앱에 접근하려면 접근 권한을 허용받아야 한다.

 

이를 위해 react-native-permission 라이브러리를 사용하면 보다 편하게 권한 설정을 요청하고 확인할 수 있다. 카메라 앱 외에도 다양한 권한을 설정하고 추가할 수 있기 때문에 많은 사람들이 사용하고 있다. 

 

1. Dependency 설치

 

먼저 개발 환경의 build package에 따라 설치를 해준다.

 

$ npm install --save react-native-permissions
# --- or ---
$ yarn add react-native-permissions

 

2. IOS 설정

 

다음은 ios 폴더의 Podfile 파일에 접근해 다음과 같이 설정을 변경한다.

 

# react-native 0.72 버전 이상일 경우, 아래의 코드를 삭제
- # Resolve react_native_pods.rb with node to allow for hoisting
- require Pod::Executable.execute_command('node', ['-p',
-   'require.resolve(
-     "react-native/scripts/react_native_pods.rb",
-     {paths: [process.argv[1]]},
-   )', __dir__]).strip

# react-native 0.72 버전 이상일 경우, 아래의 코드를 추가
+ def node_require(script)
+   # Resolve script with node to allow for hoisting
+   require Pod::Executable.execute_command('node', ['-p',
+     "require.resolve(
+       '#{script}',
+       {paths: [process.argv[1]]},
+     )", __dir__]).strip
+ end

+ node_require('react-native/scripts/react_native_pods.rb')
+ node_require('react-native-permissions/scripts/setup.rb')

 

# react-native 0.72 버전 이하일 경우, 아래의 코드를 추가
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+ require_relative '../node_modules/react-native-permissions/scripts/setup'

 

react-native 버전의 분기에 따른 설정 방법이 다르니 본인의 프로젝트에서 사용되는 react-native의 버전을 잘 확인해야 한다.

 

버전 확인하는 방법은 stackoverflow 포스팅을 참고하자.

 

이후 같은 파일 (Podfile)에 아래와 같이 필요한 권한에 따른 코드를 추가해 준다.

 

# …

platform :ios, min_ios_version_supported
prepare_react_native_project!

# ⬇️ 필요한 코드를 주석해제하여 사용
setup_permissions([
  # 'AppTrackingTransparency',
  # 'Bluetooth',
  # 'Calendars',
  # 'CalendarsWriteOnly',
  # 'Camera',
  # 'Contacts',
  # 'FaceID',
  # 'LocationAccuracy',
  # 'LocationAlways',
  # 'LocationWhenInUse',
  # 'MediaLibrary',
  # 'Microphone',
  # 'Motion',
  # 'Notifications',
  # 'PhotoLibrary',
  # 'PhotoLibraryAddOnly',
  # 'Reminders',
  # 'Siri',
  # 'SpeechRecognition',
  # 'StoreKit',
])

# …

 

카메라 앱의 경우 Camera, Microphone, PhotoLibrary (또는 PhotoLibraryAddOnly) 권한이 필요하다.

 

Microphone은 마이크 권한에 대한 설정인데, 동영상 촬영 시 필요하기 때문에 설정해 두는 것이 좋다.

 

다음은 ios > app(앱 이름) >  info.plist 파일에 대한 설정이 필요하다.

 

마찬가지로 필요로 하는 코드만을 추가해 사용하면 된다.

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

  <!-- 아래 내용 중 필요로 하는 코드만 사용 -->

  <key>NSAppleMusicUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSBluetoothAlwaysUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSBluetoothPeripheralUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSCalendarsFullAccessUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSCalendarsWriteOnlyAccessUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSCameraUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSContactsUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSFaceIDUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSLocationTemporaryUsageDescriptionDictionary</key>
  <dict>
    <key>YOUR-PURPOSE-KEY</key>
    <string>YOUR TEXT</string>
  </dict>
  <key>NSLocationWhenInUseUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSMicrophoneUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSMotionUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSPhotoLibraryUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSPhotoLibraryAddUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSRemindersFullAccessUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSSpeechRecognitionUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSSiriUsageDescription</key>
  <string>YOUR TEXT</string>
  <key>NSUserTrackingUsageDescription</key>
  <string>YOUR TEXT</string>

  <!-- … -->

</dict>
</plist>

 

사진 촬영과 마찬가지로 NSCameraUsageDescription, NSMicrophoneUsageDescription, NSPhotoLibraryUsageDescription에 대한 권한 요청 시 메시지를 <string> 태그 사이에 입력해 주면 된다.

 

간단한 예시를 들자면, Android는 별도의 메시지 설정이 되지 않기 때문에 Android와 내용을 동일하게 설정할 수 있다. "앱 이름(이)가 사진 권한을 필요로 합니다. 허용하시겠습니까?"와 같은 느낌으로, Android 구동 화면을 참고하면 된다.

 

앱 심사를 문제없이 통과하기 위해서는(반려 사유가 될 수 있고, 실제로 반려되었던 적이 있었다) 서비스와 연관되어 명확한 안내 문구를 적어주는 것이 좋다. "프로필 설정을 촬영한 사진으로 설정하기 위해 카메라 권한을 필요로 합니다."와 같은 느낌으로 적어주면 앱 심사를 반려되지 않을 수 있다.

 

3. Android 환경 설정

 

Android 환경은 설정이 더 간편하다.

 

android > app > src > main > AndroidManifest.xml 파일에 아래 코드 중 원하는 부분을 추가해 주면 된다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- 아래 코드 중 원하는 코드만 사용 (필요없는 코드는 삭제) -->

  <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
  <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
  <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
  <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
  <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
  <uses-permission android:name="android.permission.BODY_SENSORS" />
  <uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
  <uses-permission android:name="android.permission.CALL_PHONE" />
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.GET_ACCOUNTS" />
  <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
  <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
  <uses-permission android:name="android.permission.READ_CALENDAR" />
  <uses-permission android:name="android.permission.READ_CALL_LOG" />
  <uses-permission android:name="android.permission.READ_CONTACTS" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
  <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
  <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
  <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
  <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  <uses-permission android:name="android.permission.READ_SMS" />
  <uses-permission android:name="android.permission.RECEIVE_MMS" />
  <uses-permission android:name="android.permission.RECEIVE_SMS" />
  <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.SEND_SMS" />
  <uses-permission android:name="android.permission.USE_SIP" />
  <uses-permission android:name="android.permission.UWB_RANGING" />
  <uses-permission android:name="android.permission.WRITE_CALENDAR" />
  <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
  <uses-permission android:name="android.permission.WRITE_CONTACTS" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  <!-- … -->

</manifest>

 

 

사진과 동영상을 촬영하기 위해서는 android.permission.CAMERA, android.permission.READ_EXTERNAL_STORAGE, android.permission.WRITE_EXTERNAL_STORAGE 권한을 필요로 한다.

 

이때 sdk 33 버전 이상을 타깃으로 한다면, 또는 33 버전 이상도 타깃에 포함된다면 android.permission.READ_MEDIA_AUDIO, android.permission.READ_MEDIA_IMAGES, android.permission.READ_MEDIA_VIDEO에 대한 권한을 요청해야 한다.

 

최종적으로 아래와 같이 권한을 요청할 수 있다.

    <!--   ...   -->
     
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- SDK >= 33 permissions for media   -->
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
    
    <!--   ...   -->

 

 

이제 기본적인 설정이 끝났다(!)

 

해당 설정에 대한 부분은 공식 문서를 따르기만 해도 충분히 구현이 가능하니 반드시 공식문서를 잘 읽고 필요한 권한을 설정하도록 하자.

 


권한 요청하기

 

기본적인 설정을 마쳤으니, 이제는 원하는 기능 사용에 앞서 권한을 요청하기 위한 함수를 정의해야 한다.

 

react-native-permissions 라이브러리를 사용하면 requestMultiple이라는 내장 함수를 통해 여러 개의 권한을 한 번에 요청할 수 있다.

 

간단한 예시로, 카메라 권한을 요청하기 위한 코드는 아래와 같이 작성할 수 있다. (react-native, typescript 기반)

 

// 카메라 권한 요청
const requestCameraPermission = async (): Promise<boolean> => {
  try {
    const results = await requestMultiple([
      Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA,
    ]);

    return results[Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA] === RESULTS.GRANTED;
  } catch (error) {
    console.error(error);
    return false; // 예외 발생 시 false 반환
  }
};

 

ios, Android 환경에 맞게 분기를 설정하고, 이에 따라 카메라 권한을 요청한다.

 

// 비디오 권한 요청
const requestVideoPermission = async (): Promise<boolean> => {
  try {
    let permissions: Permission[];
    if (Platform.OS === 'android') {
      permissions =
        Platform.Version >= 33
          ? [
              PERMISSIONS.ANDROID.CAMERA,
              PERMISSIONS.ANDROID.READ_MEDIA_IMAGES,
              PERMISSIONS.ANDROID.READ_MEDIA_VIDEO,
              PERMISSIONS.ANDROID.READ_MEDIA_AUDIO,
            ]
          : [PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE];
    } else if (Platform.OS === 'ios') {
      permissions = [PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.PHOTO_LIBRARY];
    } else {
      return false;
    }

    const results: Record<Permission, PermissionStatus> = await requestMultiple(permissions);

    return permissions.every(permission => results[permission] === RESULTS.GRANTED);
  } catch (error) {
    console.error(error);
    return false;
  }
};

 

영상 촬영의 경우 좀 더 많은 권한을 요청해야 하는데, 방법은 근본적으로 같다.

 

권한 요청과 별도로, 사진 또는 영상을 기록할 때 권한이 올바르게 설정되어 있는지 추가적으로 확인해 주는 것이 반드시 좋다.

 

이를 위해 react-native-permissions 라이브러리의 checkMultiple 함수를 호출하여 기존 방법과 비슷하게 각 기능에 필요로 하는 권한을 확인하면 된다. 아래 코드를 참고해서 필요에 따라 수정하여 권한을 요청하면 된다.

 

// Camera 권한 확인
export const checkCameraPermission = async (): Promise<boolean> => {
  const results = await checkMultiple([Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA]);

  if (results[Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA] === RESULTS.GRANTED) {
    return true;
  } else {
    return await requestCameraPermission();
  }
};


// Video 권한 확인 및 필요 시 요청
const checkVideoPermission = async (): Promise<boolean> => {
  let permissions: Permission[];
  if (Platform.OS === 'android') {
    permissions =
      Platform.Version >= 33
        ? [
            PERMISSIONS.ANDROID.CAMERA,
            PERMISSIONS.ANDROID.READ_MEDIA_IMAGES,
            PERMISSIONS.ANDROID.READ_MEDIA_VIDEO,
            PERMISSIONS.ANDROID.READ_MEDIA_AUDIO,
          ]
        : [PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE];
  } else if (Platform.OS === 'ios') {
    permissions = [PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.PHOTO_LIBRARY];
  } else {
    console.warn('Unsupported platform');
    return false;
  }

  const results: Record<Permission, PermissionStatus> = await checkMultiple(permissions);
  const allPermissionsGranted = permissions.every(permission => results[permission] === RESULTS.GRANTED);

  if (allPermissionsGranted) {
    return true;
  } else {
    return await requestVideoPermission();
  }
};

 

위 코드는 예시일 뿐이니, 정확히 어떻게 코드가 작동하는지 알고 필요로 하는 부분만을 사용하는 것이 좋을 것 같다.

 


권한 요청 함수 호출 및 카메라 구동

 

이제 정의된 Custom Hook 함수를 호출하고 사용하면 된다!

 

함수 호출에 앞서 기본 카메라를 사용하기 위한 라이브러리를 설치하여 사용하도록 하자.

 

react-native-image-picker를 추천한다. 주간 다운로드 수도 많고, 기본 카메라 앱을 불러와 촬영 결과물의 데이터를 사용하기 용이한 편이라고 생각한다.

 

카메라 앱의 구동과 관련된 다양한 라이브러리가 있기 때문에 앱 서비스의 방향성과 기능성에 따라 프로젝트에 맞는 라이브러리를 사용하도록 하자. 예를 들어 카메라의 직접적인 기능에 관여하고 촬영에 필요로 하는 필터 등을 사용하려면 다른 라이브러리가 더 유용할 수 있다.

 

아래 코드는 react-native-image-picker를 기반으로 프로젝트에서 사용했던 코드 내용의 일부를 사용한 예시다. 

 

// recordMedia.ts
export const takePhoto = async ({ navigation, route }: ITakePhoto) => {
  const granted = await checkCameraPermission();

  if (granted) {
    await launchCamera({
      mediaType: 'photo',
      maxWidth: 1920,
      maxHeight: 1280,
      quality: 0.8,
      cameraType: 'back',
      includeBase64: false,
    }).then(() => {
        const asset = response?.assets?.[0];
        navigation.navigate('FinalConfirmation', { type: route.params.type, uri: asset.uri as string });
      }
    });
  } else {
    Alert.alert('접근 권한 에러', '현재 카메라 사용에 대한 접근 권한이 없습니다. 접근 권한을 허용해 주세요.', [
      {
        text: '확인',
        onPress: () => openSettings(),
      },
    ]);
  }
};

 

기본적으로 권한을 확인한 후 권한 확인 여부를 boolean 형태로 반환받아 해당 여부에 따라 카메라를 구동시키는데, 이때 앞서 언급한 react-native-image-picker 라이브러리의 내장 함수 launchCamera를 사용하여 촬영을 할 수 있다. 

 

launchCamera는 mediaType 매개변수가 'video' 또는 'photo'로 설정하여 동영상 또는 사진 촬영을 진행할 수 있고, 동영상의 경우 최대길이도 지정해 줄 수 있다.

 

openSettings 함수는 react-native-permissions 라이브러리의 내장 함수로 사용자의 기기에 해당 앱에 대한 설정을 열어 사용자에게 직접적으로 권한 설정을 조정하게 할 수도 있다.

 

위 코드는 단순한 예시일 뿐, 필요에 따라 store값 저장, navigator의 route에 필요한 정보를 포함시키는 등 앱 서비스의 방향성과 기능성에 따라 코드를 수정하여 권한이 승인되면 카메라를 구동시키고 해당 결괏값을 어떻게 다룰지 코드를 통해 구현하면 된다.

댓글