Разработка публикации постов с изображениями в мобильном приложении
Публикация поста с фото — функция, которая на первый взгляд выглядит простой. На практике она включает выбор изображений, кадрирование, сжатие, загрузку на сервер и создание записи в базе данных. Каждый шаг можно реализовать правильно или превратить в источник багов.
Выбор и кадрирование
PHPickerViewController (iOS 14+) — стандартный выбор. selectionLimit управляет числом фото: для постов обычно 1–10. PHPickerFilter.images фильтрует только фотографии, исключая видео и LivePhoto по умолчанию.
После выбора фото нередко нужно кадрирование. На iOS — TOCropViewController (open-source) или кастомный UIScrollView с UIImageView внутри и жестами pan/zoom. Для сохранения пропорций при кадрировании: CGRect области кропа вычисляем в координатах оригинального изображения через CGAffineTransform — не в координатах UIView, иначе при зуме будут смещения.
На Android — uCrop (Yalantis) широко используется в продакшене: поддерживает aspect ratio, ротацию, кастомный тулбар. Альтернатива — android-image-cropper.
Сжатие перед загрузкой
Стратегия: максимальная сторона 1440px для постов (в отличие от 1280px для чата — посты обычно просматриваются крупнее), качество JPEG 80%. Это даёт файл 200–400 КБ с приемлемым визуальным качеством.
На iOS UIGraphicsImageRenderer с CGSize target и jpegData(compressionQuality: 0.80). Операция выполняется в DispatchQueue.global(qos: .userInitiated). При мультивыборе (5+ фото) — последовательная обработка в фоне с прогрессом на UI.
На Android — Bitmap.createScaledBitmap() с фильтрацией true для лучшего качества масштабирования, затем compress(JPEG, 80, stream). BitmapFactory.Options.inSampleSize вычисляем заранее по метаданным, чтобы не загружать оригинал в полном размере в память.
Upload и синхронизация с постом
Изображения загружаются до создания поста. Алгоритм:
- Загружаем все фото параллельно (не последовательно) — каждое в отдельный
URLSessionUploadTask. - Собираем массив URL/ключей из ответов сервера.
- Создаём пост с этим массивом через отдельный API-запрос.
Параллельная загрузка на iOS через withTaskGroup (Swift Concurrency) или DispatchGroup. На Android — coroutineScope { launch { ... } } для каждого файла, awaitAll().
Прогресс отображаем суммарно: загружено X из N фото. Не блокируем кнопку публикации жёстко — если одно фото из пяти не загрузилось, показываем retry только для него, остальные уже готовы.
Оптимистичный post: создаём запись в локальной БД (Core Data, Room) немедленно со статусом uploading, показываем в ленте с индикатором. При успешной загрузке обновляем статус на published. При неудаче — failed с кнопкой повтора. Пользователь не ждёт — он уже видит свой пост.
Отображение в ленте
Сетка фото в посте (несколько изображений) — UICollectionView с flow layout на iOS, LazyVerticalGrid (Compose) или RecyclerView с GridLayoutManager на Android. Первое фото крупное, остальные — в сетке 2×N — классический Instagram-паттерн.
Lazy loading через Kingfisher (iOS) или Coil (Android) с disk-кэшированием. Placeholder до загрузки — blur-hash из метаданных (если сервер его возвращает) или solid color из dominant color изображения.
Сроки
Выбор фото + сжатие + upload + отображение в посте — 1–3 дня. Кадрирование + мультивыбор + оптимистичный UI — ещё 1–2 дня. Стоимость рассчитывается индивидуально.







