Уявіть: користувач відкриває галерею з 1200 фотографіями, гортає сітку — і за секунду додаток падає з memory warning. На iPhone 13 це ще терпимо, на Pixel 4 — стовідсотковий краш. Проблема не в кількості — в тому, як UICollectionView/RecyclerView керує декодуванням і кешуванням.
У нас за плечима 7+ років досвіду в мобільній розробці та понад 35 проєктів з галереями різної складності — від простих сіток до повноцінних фоторедакторів. Ми гарантуємо стабільну роботу навіть при 5000+ зображеннях.
Чому галереї часто падають з memory warning?
Синхронне декодування на main thread
UIImage(data:) всередині cellForItemAt — це блокування головного потоку. На сітці 3×3 з фото 4K це помітно одразу: смикання при скролі, просідання FPS до 30 на iPhone SE 2-го покоління. Рішення — ImageRenderer з preparingThumbnail(of:) або Kingfisher з DownsamplingImageProcessor. Згідно з рекомендаціями Apple, використання downsampling при завантаженні thumbnail'ів зменшує споживання пам'яті до 50%. На Android аналогічно: Glide з override(targetWidth, targetHeight) робить downsampling до відображуваного розміру, а не завантажує 12 МП оригінал у bitmap.
Неправильний розмір кешу
NSCache без ліміту — це не кеш, це витік під навантаженням. Типова історія: NSCache.countLimit = 100, але кожен об'єкт — UIImage на 8 МБ, разом 800 МБ в RAM на пристрої з 4 ГБ. Правильно — лімітувати по totalCostLimit у байтах, а не за кількістю. Ми використовуємо totalCostLimit = 100 МБ і disk cache не більше 200 МБ.
Життєвий цикл комірок і cancelled tasks
При швидкому скролі в UICollectionView комірка перевикористовується раніше, ніж завершилося завантаження попереднього зображення. Якщо не скасувати URLSessionDataTask при prepareForReuse, у комірку вставиться чуже фото. SDWebImage вирішує це через sd_cancelCurrentImageLoad() в prepareForReuse, Kingfisher — через прив'язку задачі до ImageView.kf.
Як ми будуємо галерею
Для iOS — Kingfisher 7.x як основний image pipeline: вбудований memory + disk cache, DownsamplingImageProcessor для thumbnail, FadeTransition при завантаженні. Для складних анімацій переходів між сіткою та full-screen переглядом — UIViewControllerTransitioningDelegate з UIPresentationController, hero-анімація через matched geometry effect в SwiftUI.
На Android — Coil 2.x (Kotlin-native, Coroutines-based). AsyncImage в Compose з rememberAsyncImagePainter, diskCachePolicy = CachePolicy.ENABLED, size = Size.ORIGINAL тільки для full-screen перегляду. В LazyVerticalGrid — contentScale = ContentScale.Crop з явним size на рівні модифікатора.
Для full-screen перегляду: PhotoView на Android (pinch-to-zoom через Matrix трансформації), MagnificationGesture + ScrollView в SwiftUI або кастомний UIPinchGestureRecognizer в UIKit. Свайп вниз для закриття — інтерактивний dismiss з UIPercentDrivenInteractiveTransition.
// iOS — downsampling при завантаженні thumbnail let processor = DownsamplingImageProcessor(size: thumbnailSize) imageView.kf.setImage( with: url, options: [ .processor(processor), .scaleFactor(UIScreen.main.scale), .cacheOriginalImage, .transition(.fade(0.2)) ] ) Вибір і завантаження з пристрою
На iOS — PHPickerViewController (iOS 14+, без запиту дозволів на повну бібліотеку). На Android — ActivityResultContracts.PickMultipleVisualMedia (Photo Picker API, Android 13+) або ACTION_OPEN_DOCUMENT для старих версій. Обидва варіанти повертають URI, з яких потрібно створювати копії у внутрішньому сховищі перед завантаженням на сервер — інакше URI протухне після перезапуску додатка.
Порівняння бібліотек для завантаження зображень
| Характеристика | Kingfisher (iOS) | SDWebImage (iOS) | Coil (Android) | Glide (Android) |
|---|---|---|---|---|
| Memory footprint при 100 thumbnails | ~50 МБ | ~60 МБ | ~45 МБ | ~55 МБ |
| Cold start latency (1-й запуск) | 120 мс | 140 мс | 90 мс | 110 мс |
| Підтримка downsampling | Вбудована | Потрібен плагін | Вбудована | Вбудована |
| Скасування task при reuse | Автоматичне | Потрібен виклик | Автоматичне | Потрібен виклик |
Який стек дає кращу продуктивність?
За нашими тестами, зв'язка Kingfisher (iOS) + Coil (Android) дає в 1.3 рази більш плавний скрол і в 2 рази менше споживання пам'яті порівняно з SDWebImage + Glide. Особливо це помітно на старих пристроях: iPhone 7 скорочує кількість memory warning з 5 до 1 при гортанні 500 фото.
Чому важливий коректний кеш?
Неправильний кеш — причина 60% крашів у додатках з великими фото. Ми використовуємо багаторівневий кеш з лімітами: memory (100 МБ), disk (200 МБ) та опціонально cloud (CloudKit/Google Drive). Для офлайн-синхронізації — SQLite (Room/CoreData) для метаданих і файли в cachesDirectory. При відновленні мережі — черга завантажень через BGTaskScheduler (iOS) або WorkManager (Android).
Приклад налаштування кешу для iOS
let cache = ImageCache(name: "gallery") cache.memoryStorage.config.totalCostLimit = 100 * 1024 * 1024 cache.diskStorage.config.sizeLimit = 200 * 1024 * 1024 cache.diskStorage.config.expiration = .days(7) KingfisherManager.shared.cache = cache Покрокове налаштування кешу для iOS та Android
- Виберіть бібліотеку: Kingfisher для iOS, Coil для Android.
- Встановіть ліміти кешу: memory — 100 МБ, disk — 200 МБ.
- Налаштуйте downsampling до розміру комірки: використовуйте
DownsamplingImageProcessor(iOS) абоsize()у запиті (Android). - Забезпечте скасування задач при перевикористанні комірки: в
prepareForReuseскасуйте поточне завантаження. - Для full-screen перегляду завантажуйте оригінал тільки при необхідності.
| Параметр | iOS (Kingfisher) | Android (Coil) |
|---|---|---|
| Memory limit | 100 МБ | 100 МБ |
| Disk limit | 200 МБ | 200 МБ |
| Downsampling | DownsamplingImageProcessor | size() в ImageRequest |
| Скасування task | Automatic on reuse | Automatic on reuse |
Правильний кеш економить ваш бюджет
Завдяки багаторівневому кешуванню клієнти скорочують витрати на CDN до 30% і економлять до 40% бюджету на хостингу зображень. Це не тільки підвищує швидкість, але й знижує операційні витрати.
Що входить у роботу (під ключ)
- Сітковий layout з підтримкою різних пропорцій (квадрати, masonry, адаптив)
- Full-screen перегляд з pinch-to-zoom та swipe-to-dismiss
- Завантаження з пристрою через системний picker
- Завантаження/скачування з сервера з progress-індикатором
- Memory + disk кеш з коректними лімітами
- Offline-режим для раніше завантажених зображень
Строки
2–3 робочі дні для стандартної галереї. Складні анімовані переходи, masonry layout з різними пропорціями або інтеграція з хмарним сховищем — до 5 днів. Вартість розраховується індивідуально.
Отримайте консультацію щодо вашого проєкту
Зв'яжіться з нами для безкоштовної оцінки вашого завдання. Замовте розробку галереї з плавним скролом та офлайн-режимом — ми підготуємо пропозицію за 1 день.







