UI는 다 준비됐고, 프로필 화면에 “Upload photo” 버튼도 붙였는데, 이제 쉬워 보이던 부분이 갑자기 쉽지 않아집니다. 실제 이미지 선택 흐름은 네이티브 권한, OS가 제어하는 인터페이스, 많은 개발자가 예상하는 것과 다른 반환 형태, 그리고 실제 빌드를 배포한 뒤에야 드러나는 자잘한 빌드 타임 디테일까지 건드리기 때문입니다.
바로 이 지점에서 Expo Image Picker가 필요합니다. 기기 라이브러리에서 이미지나 비디오를 선택하거나 카메라로 사진을 찍을 수 있도록 시스템 UI를 여는 공식 Expo 라이브러리입니다. 자세한 내용은 Expo 패키지 저장소에서 확인할 수 있습니다. 실제로 이는 네이티브 미디어 입력으로 가는 안정적인 브리지를 제공하지만, 모든 기기에서 똑같이 동작하는 커스텀 미디어 경험을 제공하는 것은 아닙니다.
이 가이드는 데모가 아닌 첫 실제 구현을 위해 작성되었습니다. 프로덕션에서 중요한 결정들에 초점을 맞춥니다. managed와 bare 워크플로우 설정, 나중에 예상치 못한 문제를 일으키지 않는 권한 처리, 안전한 결과 파싱, 그리고 사용자가 파일을 고른 뒤의 실용적인 upload 패턴까지. 커스텀 네이티브 환경에서 작업 중이라면, 이 방식이 다른 접근과 어떻게 다른지 이해해 두면 도움이 됩니다. 참고로 Capgo가 대안 가이드에서 비교한 Expo EAS update 옵션도 살펴보세요..
목차
- Expo Image Picker 시작하기
- 설치 및 필수 설정
- 카메라 및 미디어 라이브러리 접근하기
- Picker 결과 및 옵션 처리하기
- 고급 패턴과 플랫폼 차이점
- 일반적인 문제 해결
Expo Image Picker 시작하기
프로덕트 매니저가 프로필 사진을 요청했습니다. 일주일 뒤, 같은 기능에 영수증 업로드, 사고 보고용 카메라 촬영, 사용자가 처음 권한을 거부했을 때의 재시도까지 필요해집니다. 이미지 입력은 네이티브 권한, OS가 소유한 UI, 임시 파일 처리, 백엔드 업로드 흐름까지 건드리기 때문에 요구사항이 빠르게 늘어납니다.
expo-image-picker 바로 이 작업을 위한 Expo SDK 모듈입니다. 플랫폼 피커나 카메라 UI를 열고, 선택한 미디어를 React Native code에서 처리할 수 있는 형태로 돌려줍니다. JavaScript API는 간단합니다. 진짜 어려운 부분은 managed 프로젝트와 bare 프로젝트 모두에서 네이티브 설정, 권한 흐름, 결과 처리를 제대로 맞추는 것입니다.
핵심 트레이드오프는 명확합니다. 커스텀 피커를 만드는 대신 iOS와 Android가 제공하는 미디어 UI를 그대로 쓰는 것입니다. 보통 이쪽이 결과가 더 좋습니다. 사용자는 이미 시스템 화면에 익숙하고, 권한 프롬프트는 OS가 기대하는 방식으로 동작하며, 팀은 JavaScript로 갤러리를 직접 구현해 유지보수할 필요가 없습니다.
React 인터페이스가 있는 네이티브 통합 기능으로 생각하세요.
이런 관점이 도움이 되는 이유는 피커를 호출하는 버튼 자체에서 오류가 나는 경우는 드물기 때문입니다. 보통 다음 세 가지 중 하나에서 문제가 발생합니다:
- 네이티브 설정: 플러그인 설정 누락, 잘못된 권한 문자열, 또는 설정 변경 후 오래된 빌드
- 런타임 동작: 사용자가 접근을 거부하거나, iOS에서 제한된 라이브러리 접근을 허용하거나, 선택 없이 플로우를 취소하는 경우
- 결과 파싱: 현재 API는
assets배열을 반환하므로, 직접 읽는 이전 예제들은result.uri실패합니다.
워크플로우 선택은 설정 경로도 바꿉니다. Managed Expo 앱에서는 대부분의 네이티브 작업이 app config에서 처리되며, 해당 config가 변경되면 재빌드가 필요합니다. Bare 앱에서도 Expo 모듈 API를 사용할 수 있지만, 기본 iOS 및 Android 프로젝트 설정을 더 직접적으로 확인해야 합니다. 팀에서 Expo Go 대신 커스텀 클라이언트를 사용한다면, 이 가이드는 Capgo의 Expo EAS 대안 Expo Go를 넘어 네이티브 모듈 테스트가 필요한 팀을 위한.
이 구분은 가이드의 나머지 부분에서 중요합니다. 해피 패스는 이야기의 절반일 뿐이기 때문입니다. picker 구현은 두 워크플로우 모두에서 작동하고, 플랫폼별 권한 특이사항을 사용자에게 예상 밖의 일로 만들지 않으면서 처리하며, 로컬 미리보기에서 멈추지 않고 업로드 레이어에 사용 가능한 파일을 넘길 때 탄탄합니다.
설치 및 필수 설정
설치는 명령어 하나면 됩니다. picker가 실제 기기, 커스텀 dev client, 프로덕션 빌드에서 작동할지는 네이티브 설정을 제대로 하느냐에 달려 있습니다.
expo-image-picker 사진, 비디오, 카메라 캡처를 위한 플랫폼 picker 위에 React에서 쓸 수 있는 API를 제공합니다. JavaScript 호출은 간단하지만 설정은 그렇지 않습니다. 사진 및 카메라 접근은 React Native가 아니라 iOS와 Android가 제어하기 때문입니다.

Expo의 버전 인식 설치 프로그램으로 시작하세요:
npx expo install expo-image-picker
사용 expo install 를 전달하고 npm install 또는 yarn add. Expo는 SDK에 맞춰 패키지 버전을 일치시켜, 일반적인 네이티브 호환성 문제를 방지합니다. Expo 모듈이 릴리스 프로세스에 어떻게 통합되는지 비교 중이라면, 이 Expo 툴링 개요 유용한 참고 자료입니다.
Managed workflow 설정
Managed workflow에서는 Expo가 빌드 시점에 네이티브 변경 사항을 적용할 수 있도록 app config에 플러그인을 선언해야 합니다.
예시: app.json:
{
"expo": {
"plugins": ["expo-image-picker"]
}
}
이것은 최소한의 설정입니다. 실제로는 권한 안내 문구도 함께 추가하는 것이 일반적입니다. 특히 iOS에서는 시스템 프롬프트에 앱이 왜 권한을 필요로 하는지 설명해야 합니다. 문구는 사용자 작업에 맞춰 구체적으로 작성하세요. “미디어 접근 권한 필요”보다는 “프로필 사진 업로드”가 더 좋습니다.
운영상 디테일 하나가 시간을 많이 낭비하게 만듭니다. 바로 plugins권한 문자열이나 기타 네이티브 설정을 변경할 때는 다시 빌드해야 합니다. JavaScript를 리로드해도 해당 변경 사항은 적용되지 않습니다. Expo Go에서는 클라이언트에 이미 포함된 것까지만 사용할 수 있습니다. Development build나 production build에서는 새로 빌드해야 네이티브 프로젝트에 설정이 반영됩니다.
Bare React Native 설정 상세
Bare 앱에서도 API 패키지는 동일하지만, 네이티브 프로젝트를 직접 더 많이 확인해야 합니다. 가장 먼저 확인할 것은 iOS usage descriptions입니다. 앱에서 라이브러리를 열거나, 카메라를 실행하거나, 오디오와 함께 비디오를 녹화할 수 있다면, 앱에 해당 권한 문자열이 필요합니다 Info.plist 다시 빌드하기 전에.
Bare 프로젝트용 실무 체크리스트는 다음과 같습니다:
- 설치
expo-image-picker포함npx expo install expo-image-picker. - 프로젝트에서 Expo config plugins를 사용한다면 플러그인 설정을 추가하세요.
- iOS usage descriptions가 실제로 제공하는 기능과 일치하는지 확인하세요.
- 네이티브 설정을 변경했다면 iOS와 Android 앱을 다시 빌드하세요.
권한 텍스트가 누락되면 UI code도 정상이고 버튼 핸들러도 실행되기 때문에 런타임 버그처럼 보일 때가 많습니다. 실제 실패 지점은 스택 더 아래에 있습니다. 저는 보통 Info.plist앱 설정, 그리고 컴포넌트 code를 건드리기 전에 현재 빌드에 최신 네이티브 변경 사항이 포함되어 있는지를 확인합니다.
몇 가지 습관만 들이면 설정이 훨씬 예측 가능해집니다:
- 실제 동작에 맞춰 권한 텍스트 작성: 사용자가 왜 prompt가 뜨는지 이해할 수 있어야 합니다.
- 카메라와 라이브러리를 각각 설정하세요: 한쪽은 정상 작동해도 다른 쪽은 여전히 실패할 수 있습니다.
- 네이티브 변경 후 다시 빌드: hot reload와 fast refresh는 native permissions를 업데이트하지 않습니다.
- 기기에서 테스트: simulator에서는 권한 및 카메라 관련 이슈가 숨겨질 수 있습니다.
개발 중에는 picker가 잘 작동하는데 TestFlight나 Play Store 빌드에서 문제가 발생한다면, 우선 설정 문제로 간주하세요. 대부분의 경우 설정 문제입니다.
카메라 및 미디어 라이브러리 접근하기
사용자가 “Upload photo”를 누르면 카메라나 라이브러리가 열리기를 기대합니다. 이때 앱의 역할은 명확합니다. 올바른 시스템 UI를 열고, 화면 오류 없이 거부나 취소를 처리하며, 미리보기나 upload에 사용할 수 있는 로컬 파일 참조를 반환하는 것입니다.
iOS와 Android 모두에서 managed 및 bare build를 테스트해 보기 전까지는 간단해 보일 수 있습니다. JavaScript API는 간결하게 유지되지만, 런타임 동작은 여전히 OS 프롬프트, 기기 하드웨어, 그리고 앞서 설정한 native permissions에 따라 달라집니다.

간단하지만 안전한 컴포넌트
핵심 흐름은 Expo managed 및 bare workflow 프로젝트 모두 동일합니다. 관련 권한을 요청하고, picker를 실행한 뒤, 사용자가 취소했는지 확인하고, 첫 번째 에셋을 읽어옵니다. result.assets.
기본 컴포넌트는 다음과 같습니다:
import { useState } from 'react';
import { View, Button, Image, Alert } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function PhotoInput() {
const [imageUri, setImageUri] = useState<string | null>(null);
const pickFromLibrary = async () => {
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permission.granted) {
Alert.alert('Permission required', 'Please allow photo library access.');
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (result.canceled) return;
const asset = result.assets?.[0];
if (!asset?.uri) return;
setImageUri(asset.uri);
};
const takePhoto = async () => {
const permission = await ImagePicker.requestCameraPermissionsAsync();
if (!permission.granted) {
Alert.alert('Permission required', 'Please allow camera access.');
return;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
quality: 1,
});
if (result.canceled) return;
const asset = result.assets?.[0];
if (!asset?.uri) return;
setImageUri(asset.uri);
};
return (
<View>
<Button title="Choose from library" onPress={pickFromLibrary} />
<Button title="Take photo" onPress={takePhoto} />
{imageUri ? (
<Image
source={{ uri: imageUri }}
style={{ width: 200, height: 200 }}
/>
) : null}
</View>
);
}
여기서 세 가지 세부 사항이 중요합니다.
- 라이브러리와 카메라 권한을 각각 요청하세요. 두 권한은 독립적으로 실패합니다.
- 취소는 오류 상태가 아니라 정상적인 사용자 동작으로 처리하세요.
- 다음에서 읽기
assets[0], 선택기가 최상위 배열이 아니라 에셋 배열을 반환하기 때문입니다uri.
라이브러리 및 카메라 플로우
작동하는 기능을 가장 빨리 구현하려면 라이브러리 플로우부터 시작하세요. 테스트하기 쉽고, 더 많은 시뮬레이터 환경에서 작동하며, 카메라 하드웨어의 엣지 케이스를 피할 수 있습니다. 결과 처리 경로가 안정화된 후에 카메라 지원을 추가하세요.
카메라 경로는 개발 중 실패할 수 있는 경우가 더 많습니다. iOS 시뮬레이터 지원은 제한적입니다. Android 에뮬레이터는 실제 기기와 같은 카메라 동작을 제공하지 않을 수 있습니다. bare 프로젝트에서는 이런 차이 때문에 실제 원인이 네이티브 설정이나 테스트 환경인데도 컴포넌트 code를 확인하게 될 수 있습니다.
깔끔한 UI 패턴은 피커를 호출하기 전에 소스를 먼저 사용자에게 묻는 것입니다 API:
const showPickerOptions = () => {
Alert.alert('Upload image', 'Choose a source', [
{ text: 'Camera', onPress: takePhoto },
{ text: 'Photo Library', onPress: pickFromLibrary },
{ text: 'Cancel', style: 'cancel' },
]);
};
이러한 분리는 각 함수의 역할을 명확히 합니다. 나중에 분석, 기능 플래그 또는 백엔드 특정 규칙을 추가하기도 쉬워집니다. 예를 들어, 일부 팀은 프로필 사진에는 라이브러리 업로드를 허용하되, 신원 확인에는 반드시 카메라로 촬영한 새 이미지를 요구합니다.
앱 전체가 Expo 외부의 파일 접근 패턴을 지원하거나 네이티브 스택 간 규약을 비교 중이라면 이 Capacitor 사진 라이브러리 참고 자료 가 유용한 맥락이 됩니다.
팀원이나 QA에게 이 흐름을 보여줄 때는 짧은 데모가 큰 도움이 됩니다:
시스템 UI에서 기대할 수 있는 것
expo-image-picker 플랫폼 피커나 카메라 UI가 열립니다. 앱이 해당 흐름의 모든 화면을 제어하는 것은 아닙니다. 이 차이가 중요한 이유는 “내 기기에서는 잘 된다”는 말이 종종 “OS가 내가 테스트한 경로를 허용했다”는 뜻이기 때문입니다.
iOS에서는 사용자가 전체 액세스 대신 제한된 라이브러리 액세스 권한만 부여할 수 있습니다. Android의 경우, OS 버전과 제조사 스킨에 따라 피커 동작이 달라질 수 있습니다. Managed workflow 프로젝트에서는 Expo가 네이티브 연결 작업을 대신 처리해 줍니다. Bare workflow 프로젝트에서는 빌드된 앱에 변경한 네이티브 권한 설정이 포함되었는지 확인해야 합니다. JavaScript 호출부는 두 경우 모두 동일할 수 있지만, 런타임 결과는 달라질 수 있습니다.
저는 보통 기능을 완료했다고 판단하기 전에 다음 케이스들을 테스트합니다:
- 최초 권한 요청
- 권한 거부
- 사용자 취소
- 라이브러리 선택 성공
- 실제 기기에서 카메라 촬영 성공
- 반환된 로컬 URI 즉시 미리보기
이 케이스들은 실제 프로덕션 동작과 직결됩니다. 또한 파일을 서버, 검수 pipeline, 또는 다음과 같은 게시 엔드포인트로 보내야 한다면 다음 단계로 깔끔하게 이어집니다. Instagram 미디어 게시 API.
Picker 결과 및 옵션 처리하기
Picker 결과는 실제 프로덕션 로직이 필요한 부분입니다. 시스템 UI는 단순한 파일 경로가 아닌 구조화된 객체를 반환하며, 여기서 작은 실수 하나가 미리보기 깨짐, 빈 업로드, 사용자가 취소한 뒤의 크래시로 이어집니다.
결과 객체를 올바르게 읽기
현재 Expo 앱에서 중요한 결과 구조는 result.assets[0].uri이며, 최상위 result.uri가 아닙니다. 네이티브 설정은 다르지만 JavaScript API은 동일하므로, 이 디테일은 매니지드 워크플로우와 베어 워크플로우 프로젝트 모두에 영향을 줍니다.
가드 우선 패턴을 사용하세요:
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (result.canceled) {
return;
}
const asset = result.assets?.[0];
if (!asset) {
return;
}
const { uri } = asset;
setImageUri(uri);
이 패턴은 가장 자주 보는 두 가지 실패 사례를 처리합니다. 취소된 피커는 읽을 에셋을 제공하지 않으며, code이 result.assets[0] 이 항상 존재한다고 가정하면 런타임에서 실패합니다.
URI를 확보했다면 미리보기 렌더링은 간단합니다:
<Image source={{ uri: imageUri }} style={{ width: 240, height: 240 }} />
나중에 업로드할 계획이라면 전체 asset URI만이 아니라 그 주변 객체까지 포함합니다. 실제로, fileName, mimeType, width, height, 그리고 fileSize 검증, 로깅, 더 깔끔한 multipart 요청 구성에 자주 유용합니다.
다운스트림 동작을 변경하는 옵션
일부 피커 옵션은 선택 화면 이상에 영향을 줍니다. 파일 크기, 편집 동작, 백엔드가 받아들여야 하는 항목까지 좌우합니다.
| 옵션 | 유형 | 변경 사항 | 일반적인 용도 |
|---|---|---|---|
mediaTypes |
배열 | 사용자의 선택 범위 제한 | API에서 이미지만 허용하는 경우, 선택을 이미지로 제한하세요 |
allowsEditing |
boolean | 지원되는 경우 OS가 자르기 또는 편집 UI를 제공하도록 합니다 | 아바타, 정사각형 커버, 영수증 촬영 |
quality |
number | 지원되는 이미지 출력을 압축합니다 | 모바일 네트워크에서 업로드 크기를 줄입니다 |
base64 |
boolean | 결과에 인코딩된 이미지 데이터를 추가합니다 | 인라인 이미지 데이터가 명시적으로 필요한 통합 전용 |
놓치기 쉬운 몇 가지 트레이드오프가 있습니다:
allowsEditing이미지 슬롯의 모양이나 크기가 고정된 경우 유용합니다. 서버에서 자체 크롭 파이프라인을 처리하고 원본 파일이 필요한 경우에는 덜 유용합니다.quality업로드 시간, 메모리 압박, 서버 스토리지에 영향을 줍니다.quality: 1자동으로 올바른 선택은 아닙니다.mediaTypes백엔드 규칙과 맞아야 합니다. 서버가 비디오를 거부한다면 피커가 비디오를 반환하지 않게 하세요.base64메모리 내 페이로드 크기를 늘립니다. 수신 서비스가 요구하지 않는 한 피하세요.
마지막 내용은 메모리가 적은 기기에서 중요합니다. 미리보기와 multipart upload에는 보통 로컬 파일 URI를 넘기는 게 더 좋습니다. Base64도 쓸 곳은 있지만 파일 참조를 넘기는 것보다 비용이 큽니다.
URI vs base64
대부분의 앱에서 규칙은 간단합니다:
- 사용 URI 미리보기용.
- 사용 URI 파일 업로드용.
- 사용 base64 수신 시스템이 인코딩된 콘텐츠를 명시적으로 요청할 때만 사용하세요.
이 패턴은 picker code를 작게 유지하고 테스트하기 쉽게 만듭니다. 또한 결국 외부 플랫폼(예: ...)에 게시하는 서비스를 포함해, 많은 백엔드 미디어 플로우가 구성되는 방식과도 일치합니다. Instagram 미디어 게시 API.
팀이 OTA 업데이트를 자주 배포하거나 이미지가 많은 에셋을 앱 전달 과정에서 처리한다면, 여기서의 파일 크기 결정은 파이프라인 나머지에도 영향을 줍니다. 이 가이드에서 다루는 앱 업데이트용 이미지 최적화 picker 설정에 유용한 보조 도구입니다.
실제 앱을 위한 더 안전한 결과 패턴
데모 code에서는 저장만 imageUri 하면 충분합니다. 프로덕션에서는 정규화된 객체를 저장해 다음 단계(미리보기, 검증, 업로드, 재시도)에서 매번 raw picker 응답을 다시 해석할 필요가 없도록 하세요.
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 0.8,
});
if (result.canceled || !result.assets?.length) {
return;
}
const asset = result.assets[0];
setSelectedImage({
uri: asset.uri,
fileName: asset.fileName ?? 'upload.jpg',
mimeType: asset.mimeType ?? 'image/jpeg',
width: asset.width,
height: asset.height,
fileSize: asset.fileSize ?? null,
});
이렇게 하면 앱 안에서 예측 가능한 단일 구조를 얻을 수 있습니다. 또한 다른 곳에서 네이티브 차이를 처리하는 동안 앱의 code가 안정적으로 유지되므로 managed와 bare 프로젝트를 일관되게 유지하기가 더 쉽습니다.
마지막으로 한 가지 확인이 도움이 됩니다. 만약을 대비해 추가 결과 필드를 켜지 마세요. 필요한 데이터만 요청하고, picker를 일반적인 파일 처리 단계로 만들기보다 선택에 집중시키세요.
고급 패턴과 플랫폼 차이점
picker 기능은 처음 선택한 이미지가 재시도, 인증 헤더, 네이티브 권한 차이, 실제 업로드 엔드포인트를 버텨야 하는 순간부터 복잡해집니다. expo-image-picker 는 선택을 잘 처리합니다. 나머지 기능은 앱의 몫입니다.

실용적인 업로드 패턴
파일 업로드를 기대하는 API라면, FormData 여전히 가장 안전한 기본값입니다. Rails, Node, Laravel, Django, Go 등 일반적인 백엔드에서 모두 동작하며, 픽서를 전송 관련 로직과 분리해 줍니다.
async function uploadImage(imageUri: string) {
const formData = new FormData();
formData.append('file', {
uri: imageUri,
name: 'upload.jpg',
type: 'image/jpeg',
} as any);
const response = await fetch('https://your-api.example.com/uploads', {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
},
});
if (!response.ok) {
throw new Error('Upload failed');
}
return response.json();
}
이 code 만으로도 경로가 정상 작동함을 증명할 수 있지만, 프로덕션 앱은 보통 한 단계 더 필요합니다. 가능하면 선택된 자산에서 name 및 type 를 파생하고, 인증은 픽서 함수 외부에서 처리하며, 업로드 상태를 픽서 상태와 분리해 요청 실패 시 사용자가 라이브러리를 다시 열지 않도록 하세요.
리뷰에서 자주 발견하는 일반적인 실패를 방지하기 위한 몇 가지 체크리스트입니다:
- 로컬 파일 존재 여부를
uri요청 빌드 전에 확인하세요 - 업로드 전에 미리보기를 렌더링해 사용자가 잘못된 파일을 일찍 알아채게 하세요
- 요청이 진행 중일 때 중복 탭을 방지하세요
- 네트워크 실패는 피커 취소나 권한 오류와 따로 처리하세요
- 백엔드 검증에서 대용량 파일, 지원되지 않는 MIME 타입, 인증 누락을 거부할 수 있다고 예상하세요
백엔드에서 multipart 대신 base64를 요구한다면, 이는 보통 피커의 요구사항이 아니라 서버 제약입니다. 모바일에서는 multipart가 메모리 부담이 적고 로직을 따져보기도 더 쉽습니다.
플랫폼 차이가 실제로 중요한 지점
피커 UI는 네이티브이므로 네이티브 동작을 그대로 따릅니다. 이는 사용자가 보는 화면뿐 아니라 code이 가정해야 할 동작에도 영향을 줍니다.
iOS에서는 편집 흐름과 권한 프롬프트가 Apple의 관례를 따릅니다. 사진 접근이 제한(Limited Photos)되면 권한이 완전히 허용된 기기의 테스트 계정에서 보던 것보다 더 적은 에셋이 반환될 수 있습니다. Android에서는 OS 버전과 제조사 스킨에 따라 피커 동작이 더 다양하게 나타나며, 특히 앨범, 파일 이름, 카메라 캡처 반환 방식에서 그렇습니다. Bare React Native 앱은 네이티브 설정을 직접 관리하므로 이런 차이를 더 직접적으로 겪지만, managed Expo 앱도 피커를 완벽히 균일한 도구가 아닌 플랫폼별 특성을 지닌 도구로 다루는 code이 필요합니다.
실무 규칙은 간단합니다. 기기 간에 동일한 UI나 메타데이터가 아니라 검증할 수 있는 필드에 의존하세요.
실제 앱에서 중요한 몇 가지 예시는 다음과 같습니다:
- 편집 및 크롭: iOS와 Android 간 UI 및 크롭 동작이 동일하지 않습니다
- 반환되는 메타데이터:
fileName,mimeType, 그리고fileSize없거나 일관성이 없을 수 있으니 폴백을 추가하세요 - 권한: iOS의 사진 접근은 선택한 항목으로 제한될 수 있고, Android 동작은 OS 버전과 시스템 피커 지원에 더 크게 좌우됩니다
- 카메라 출력: 촬영한 이미지는 라이브러리 자산과 이름, 방향, 압축 특성이 다를 수 있습니다
팀이 Expo 외부에서도 작업한다면, 이 DesignStack 앱 개발 가이드 는 단일 라이브러리를 넘어 미디어 처리 결정에 유용한 Android 맥락을 제공합니다.
Managed vs. bare workflow 차이점
이 시점부터는 설정 방식이 운영 측면에서 중요해지기 시작합니다.
Managed workflow에서는 권한 문자열과 플러그인 설정이 주로 app config에 포함되며, 새로운 build를 생성할 때 native 변경 사항이 적용됩니다. 이는 JavaScript 영역을 깔끔하게 유지해주지만, 설정 수정 사항이 다음 native build 전까지는 반영되지 않음을 의미합니다. OTA 업데이트로는 누락된 native 권한을 패치할 수 없습니다.
Bare workflow에서는 동일한 기능이라도 고려해야 할 요소가 더 많습니다. iOS usage descriptions, Android manifest 동작, 패키지 설치 및 rebuild 타이밍을 직접 확인해야 합니다. 장점은 제어권이 높다는 것이지만, 단점은 picker 이슈가 JavaScript 호출부가 아닌 native 설정 때문에 발생할 수 있다는 점입니다.
Expo와 Capacitor 사이를 전환하는 팀은 이 추상화 계층이 얼마나 다른지 종종 과소평가합니다. Capgo은 유용한 설명을 제공합니다. Capacitor가 플랫폼 차이를 처리하는 방식에 대한설명은 팀이 native setup을 어느 정도 직접 관리할지 결정할 때 좋은 비교 기준이 됩니다.
두 workflow 모두에서 제 선호는 일관됩니다. picker code 범위를 좁게 유지하고, 결과는 한 번만 정규화하며, 전용 API 계층을 통해 upload하세요. 플랫폼별 동작은 가정으로 얼버무리기보다 명시적으로 구성하고 테스트할 대상으로 다뤄야 합니다.
일반적인 문제 해결
대부분의 Expo Image Picker 버그는 몇 가지 카테고리로 나뉩니다. 가장 빠른 해결 방법은 config, 권한, 결과 처리, 또는 렌더링 중 어느 계층에서 문제가 발생하는지 파악하는 것입니다.

일반적인 실패에 대한 빠른 확인
피커가 열리지 않거나 권한 요청이 실패하면 먼저 네이티브 설정을 확인하세요. 특히 bare 앱에서는 iOS 사용 설명 문구 누락이 흔한 원인입니다.
사용자가 피커를 닫은 후 앱이 충돌하면 결과 처리 로직을 점검하세요. 많은 구현이 여전히 직접 URI를 가정하고 canceled 검사를 건너뜁니다.
몇 가지 빠른 매핑을 참고하세요:
- 권한 거부 오류: 앱 설정과 네이티브 권한 문자열을 확인한 후 다시 빌드하세요.
undefined이미지 URI: 다음에서 읽기result.assets?.[0]?.uri, 아니라result.uri.- 취소 후 아무 반응이 없을 때: 정상적인 동작일 수 있습니다. 취소는 no-op 상태로 처리하세요.
- 이미지가 렌더링되지 않을 때: URI가 state에 저장되어 전달되었는지 확인하세요
<Image source={{ uri }} />. - 시뮬레이터에서 카메라가 이상하게 동작할 때: 라이브러리 버그를 추적하기 전에 실제 기기에서 테스트하세요.
간단한 프로덕션 체크리스트
배포 전 최종 점검으로 사용하세요:
- Expo 도구로 설치: 사용
npx expo install expo-image-picker. - 네이티브 구성 요소 설정: 플러그인 및 필요한 권한 설명 추가.
- 권한을 의도적으로 요청: 카메라와 미디어 라이브러리 플로우 분리.
- 모든 결과 가드: 체크 항목
result.canceled안전하게 읽기assets[0]. - URI 기반 업로드를 권장합니다: base64는 특수한 경우에만 사용하세요.
- 실제 기기에서 테스트하세요: 특히 카메라 캡처와 권한 프롬프트를 확인할 때.
팀이 React Native 프로젝트와 함께 Capacitor 또는 Electron 앱을 배포한다면, Capgo 스토어 리뷰를 기다리지 않고 JavaScript, CSS, 설정, 에셋 업데이트를 배포하는 방법 중 하나입니다. 업로드 UI, 검증 규칙, 카피, 피커 플로우 주변의 에셋 처리처럼 이미지 관련 수정이 웹 레이어에 있을 때 유용합니다.