Інтеграція камери (фото) в мобільний додаток
На перший погляд задача несложна — відкрити камеру, зробити снимок, отримати файл. На практиці це ланцюжок з дозволів, делегатів, обробки орієнтації і завантаження на сервер, де кожен крок може сломатися по-своєму.
Де частіше всього виникають проблеми
Дозволи. iOS вимагає опису в Info.plist (NSCameraUsageDescription, NSPhotoLibraryUsageDescription), інакше краш без зрозумілого логу. Android до API 23 дозволи були статичні, з API 23+ — runtime-запит через ActivityResultContracts.RequestPermission. Якщо не обробити shouldShowRequestPermissionRationale, користувач натискає «Запретити навсегда» — і додаток мовчазно не отримує доступ до камери без пояснення.
EXIF Ориентация. Пристрій фізично повертає матрицю, але JPEG при цьому записується «набік», а правильна орієнтація зберігається в EXIF-тегу Orientation. UIImageView на iOS показує картинку нормально завдяки автоматичній коррекції, але як тільки фото йде на сервер, Node.js або Python читає байти без урахування EXIF — і аватарка повернена на 90°. На Android аналогічна ситуація з ExifInterface з androidx.exifinterface.
Вибір із галереї. PHPickerViewController з PHPickerFilter.videos на iOS 14+ працює без запиту доступу до Photo-бібліотеки. Для старіших версій — UIImagePickerController з .photoLibrary. На Android — ActivityResultContracts.PickVisualMedia (Photo Picker API, доступен з Android 13, бекпортован через androidx.activity:activity:1.7+).
Як реалізуємо
iOS (Swift, нативно). Відкриваємо UIImagePickerController або PHPickerViewController. У делегаті picker(_:didFinishPicking:) отримуємо NSItemProvider, викликаємо loadObject(ofClass: UIImage.self). Перед збереженням або відправкою — нормалізуємо орієнтацію через imageFixedOrientation() (рисуємо в UIGraphicsImageRenderer з правильним CGAffineTransform за EXIF). Компресія: jpegData(compressionQuality: 0.82) — емпірично підібраний баланс між вагою і якістю для аватарок/превью.
Android (Kotlin). CameraX — стандарт для захисту фото через ImageCapture.takePicture(). Отримуємо ImageProxy, конвертуємо через toBitmap(), застосовуємо ExifInterface для читання орієнтації і Matrix.postRotate() для коррекції. Для вибору з галереї — PickVisualMedia з ActivityResultContracts.
Flutter. image_picker (pub.dev) — де-факто стандарт. Під капотом викликає нативні пікери платформи. Додатково: flutter_image_compress для ресайзу перед завантаженням. На iOS потрібно додати ключі в Info.plist навіть при використанні плагіна.
React Native. react-native-image-picker або expo-image-picker (для Expo-проектів). Повертає uri, width, height, fileSize. Орієнтація не завжди корректується автоматично — react-native-image-resizer допоможе.
Завантаження на сервер
Мультипарт-завантаження через URLSession.shared.uploadTask (iOS) або OkHttp MultipartBody (Android). Для великих файлів реалізуємо прогрес через делегат/listener. При нестабільному з'єднанні — фонова завантаження через URLSessionConfiguration.background на iOS або WorkManager + CoroutineWorker на Android.
Кастомний інтерфейс камери
UIImagePickerController виглядає як системна камера і не підтримує довільний UI. Для кастомного інтерфейсу — AVCaptureSession напрямо: AVCaptureDeviceInput → AVCapturePhotoOutput, превью через AVCaptureVideoPreviewLayer. Це дозволяє додати рамку для сканування документа, кнопку переключення між камерами з анімацією, сітку правила третей.
На Android кастомна камера — CameraX Preview + CameraX ImageCapture в одному ProcessCameraProvider. PreviewView рендерит потік з камери, поверх — власні view-елементи.
Строки
Базова інтеграція (камера + галерея + завантаження) — 1–2 дні. Якщо потрібен кастомний інтерфейс камери з наложенням рамки для документів або нормалізація EXIF на всіх пристроях — 3 дні.







