블로그 목록

expo-image-picker로 사진 올리기 — 갤러리·카메라에서 이미지 받기

프로필 사진 변경 기능을 붙이려는데, 막상 시작하려니 손이 안 나갔다. 갤러리 열고, 카메라 띄우고, 권한 물어보고, 취소했을 때 처리하고... 앱마다 다 하는 이 흔한 기능이 은근히 손이 많이 간다. 다행히 Expo에서는 expo-image-picker 하나면 대부분 끝난다.

설치

npx expo install expo-image-picker

app.json에 권한 설명 문구를 넣어두는 걸 잊으면 iOS에서 앱이 그냥 죽는다. 플러그인으로 넣어주는 게 편하다.

{
  "expo": {
    "plugins": [
      ["expo-image-picker", {
        "photosPermission": "프로필 사진을 고르기 위해 사진 접근이 필요합니다."
      }]
    ]
  }
}

갤러리에서 고르기

핵심은 launchImageLibraryAsync 하나다. 권한은 라이브러리 접근의 경우 최근 iOS/Android에서는 시스템 시트가 알아서 처리해줘서 별도로 요청하지 않아도 되는 경우가 많다.

import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { Image, Button, View } from 'react-native';

export default function ProfilePhoto() {
  const [uri, setUri] = useState<string | null>(null);

  const pick = async () => {
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ['images'],
      allowsEditing: true,
      aspect: [1, 1],
      quality: 0.7,
    });

    if (!result.canceled) {
      setUri(result.assets[0].uri);
    }
  };

  return (
    <View>
      {uri && <Image source={{ uri }} style={{ width: 120, height: 120 }} />}
      <Button title="사진 선택" onPress={pick} />
    </View>
  );
}

처음에 헷갈렸던 게 결과 구조다. 예전 result.uri가 아니라 이제는 result.assets[0].uri다. 여러 장 선택(allowsMultipleSelection)을 지원하면서 배열로 바뀌었기 때문이다. 그리고 사용자가 취소하면 canceledtrue로 오니까, 이 분기를 꼭 먼저 걸러줘야 한다. 안 그러면 assetsnull이라 바로 터진다.

mediaTypes도 예전엔 MediaTypeOptions.Images 같은 enum이었는데 지금은 ['images'] 문자열 배열로 바뀌었다. 옛날 예제 보고 그대로 쓰면 경고가 뜬다.

카메라로 찍기

갤러리 대신 즉석에서 찍게 하려면 launchCameraAsync를 쓴다. 카메라는 권한을 명시적으로 물어봐야 한다.

const takePhoto = async () => {
  const { granted } = await ImagePicker.requestCameraPermissionsAsync();
  if (!granted) return;

  const result = await ImagePicker.launchCameraAsync({
    allowsEditing: true,
    quality: 0.7,
  });
  if (!result.canceled) setUri(result.assets[0].uri);
};

서버로 올리기

받은 uri는 로컬 파일 경로라서, 서버에 보낼 땐 FormData에 담아 보낸다.

const form = new FormData();
form.append('file', {
  uri,
  name: 'profile.jpg',
  type: 'image/jpeg',
} as any);

await fetch('https://api.example.com/upload', {
  method: 'POST',
  body: form,
});

quality를 0.7 정도로 낮춰두면 원본 그대로 올리는 것보다 업로드가 훨씬 빠르다. 프로필 사진 수준이면 화질 차이도 거의 안 느껴진다.


정리하면 챙길 건 세 가지다. canceled 분기 먼저 확인하기, 결과는 assets[0].uri로 꺼내기, 카메라만 권한 명시 요청하기. 이 세 개만 알면 사진 올리기 기능은 30분이면 붙인다.