Урок 02

Камера и галерея

Работа с изображениями — частая задача в мобильных приложениях.

Expo Image Picker

npx expo install expo-image-picker

Выбор из галереи

import * as ImagePicker from 'expo-image-picker';

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

    if (!result.canceled) {
        const asset = result.assets[0];
        console.log(asset.uri, asset.width, asset.height);
    }
}

Съёмка

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

    const result = await ImagePicker.launchCameraAsync({
        mediaTypes: ['images'],
        allowsEditing: true,
        quality: 0.8,
    });

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

Несколько изображений

const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ['images'],
    allowsMultipleSelection: true,
    selectionLimit: 10,
});

Загрузка на сервер

async function uploadImage(uri: string) {
    const formData = new FormData();

    formData.append('file', {
        uri,
        name: 'photo.jpg',
        type: 'image/jpeg',
    } as unknown as Blob);

    const res = await fetch('https://api.example.com/upload', {
        method: 'POST',
        body: formData,
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    return res.json();
}

Не устанавливай Content-Type — fetch сам выставит multipart/form-data с boundary.

Кастомная камера

Для полного контроля — expo-camera:

npx expo install expo-camera
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useRef } from 'react';

function Camera() {
    const [permission, requestPermission] = useCameraPermissions();
    const ref = useRef<CameraView>(null);

    if (!permission?.granted) {
        return <Button onPress={requestPermission} title="Разрешить камеру" />;
    }

    async function takePicture() {
        const photo = await ref.current?.takePictureAsync();
        console.log(photo?.uri);
    }

    return (
        <View style={{ flex: 1 }}>
            <CameraView ref={ref} style={{ flex: 1 }} facing="back" />
            <Pressable onPress={takePicture} style={styles.button}>
                <Text>Снять</Text>
            </Pressable>
        </View>
    );
}

Видео

const result = await ImagePicker.launchCameraAsync({
    mediaTypes: ['videos'],
    videoMaxDuration: 60,
    quality: 0.8,
});

Превью

function Preview({ uri, onRemove }: { uri: string; onRemove: () => void }) {
    return (
        <View style={styles.preview}>
            <Image source={{ uri }} style={styles.image} />
            <Pressable onPress={onRemove} style={styles.remove}>
                <Ionicons name="close" size={20} color="#fff" />
            </Pressable>
        </View>
    );
}

Сжатие

import * as ImageManipulator from 'expo-image-manipulator';

const { uri } = await ImageManipulator.manipulateAsync(
    originalUri,
    [{ resize: { width: 1080 } }],
    { compress: 0.8, format: ImageManipulator.SaveFormat.JPEG }
);

Разрешения

{
    "expo": {
        "ios": {
            "infoPlist": {
                "NSCameraUsageDescription": "Камера для съёмки фото",
                "NSPhotoLibraryUsageDescription": "Галерея для выбора фото"
            }
        }
    }
}

Итоги

  • expo-image-picker для галереи и камеры
  • expo-camera для кастомного UI
  • FormData для загрузки
  • Не устанавливай Content-Type
  • expo-image-manipulator для сжатия
  • Разрешения в app.json