Трейдер хоче бачити курс біткоїна на головному екрані, не відкриваючи додаток. Але віджет криптовалюти — не просто гарна картинка: він має жити за правилами мобільної ОС. На iOS це WidgetKit + SwiftUI, на Android — Jetpack Glance або класичний AppWidgetProvider. Обидва працюють за принципом snapshot: система запитує актуальний UI в певні моменти, і те, що відобразиться — наша відповідальність. Спираємося на понад 30 реалізованих рішень для криптобірж та 5+ років досвіду мобільної розробки. Оптимізація бюджету проєкту починається з правильного вибору архітектури віджета.
Чому віджет на Home Screen складніший, ніж здається?
На перший погляд віджет здається простим UI-елементом. Але його обмеження щодо оновлення даних, особливо для криптовалют, потребують продуманої архітектури. Розберемо ключові проблеми та їх рішення — на власному досвіді.
Як WidgetKit обмежує оновлення?
WidgetKit не дозволяє віджету робити мережеві запити в реальному часі. Віджет отримує дані через TimelineProvider, який повертає масив TimelineEntry з заздалегідь підготовленими даними та часовими мітками. Система сама вирішує, коли перемалювати віджет.
Для криптовіджета типова стратегія — оновлення кожні 15–30 хвилин через TimelineReloadPolicy.atEnd або .after(date:):
struct CryptoPriceEntry: TimelineEntry {
let date: Date
let symbol: String
let price: Decimal
let change24h: Double
}
struct CryptoPriceProvider: TimelineProvider {
func getTimeline(in context: Context,
completion: @escaping (Timeline<CryptoPriceEntry>) -> Void) {
Task {
let price = try? await CryptoAPIClient.shared.fetchPrice(symbol: "BTC")
let entry = CryptoPriceEntry(date: .now,
symbol: "BTC",
price: price?.usd ?? 0,
change24h: price?.change24h ?? 0)
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
}
Детальніше про механізм TimelineProvider
`TimelineProvider` — це протокол, який визначає три методи: `placeholder`, `getSnapshot` та `getTimeline`. `getTimeline` повертає масив записів, кожен з яких містить дату та дані. Система використовує ці записи для відображення віджета у відповідні моменти часу. Після відображення останнього запису віджет запитує новий timeline. Цей цикл дозволяє економити ресурси, але обмежує частоту оновлень.
Важливий нюанс: Apple регулює бюджет оновлень. Віджет з високою частотою оновлень на пристроях з низьким зарядом батареї отримує урізаний бюджет — оновлення починають приходити рідше, ніж запрошено. Для трейдингових додатків з вимогою «дані не старше 1 хвилини» віджет не підходить — потрібно чесно пояснити це клієнту до початку розробки. Ми завжди аналізуємо бізнес-вимоги та пропонуємо альтернативи, наприклад, push-сповіщення або Live Activity. Дотримання гайдлайнів допомагає уникнути відхилення в App Store та економить бюджет на доопрацювання.
Передача даних між основним додатком та віджетом — через App Groups + UserDefaults(suiteName:) або FileManager з shared container. @AppStorage всередині віджета працює тільки з App Group suite — без цього віджет не побачить дані, записані основним додатком.
Розміри та адаптація UI
WidgetKit підтримує 4 розміри: .systemSmall, .systemMedium, .systemLarge, .systemExtraLarge (тільки iPad). Для криптовіджета зазвичай робимо small (символ + ціна + зміна) та medium (кілька монет у рядок). SwiftUI у віджеті не підтримує анімації, ScrollView, натискання на довільні області — тільки Link для deep link.
Як Android вирішує ті самі завдання?
Jetpack Glance проти класичного AppWidgetProvider
| Характеристика |
Jetpack Glance |
AppWidgetProvider |
| API |
Compose-подібний |
RemoteViews |
| Дата появи |
Відносно недавно |
З самого початку |
| Складність |
Нижча (декларативний) |
Вища (імперативний) |
| Обмеження |
Не всі Compose-модифікатори |
Повний контроль |
Jetpack Glance — Compose-подібний API для віджетів, з'явився відносно недавно. Помітно зручніший за класичний RemoteViews, але має обмеження: підтримуються не всі Compose-модифікатори, а частина API працює інакше, ніж у звичайному Compose.
Оновлення даних через GlanceAppWidgetManager.updateIf + WorkManager з періодичним завданням:
class CryptoPriceWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
val prefs = currentState<Preferences>()
val price = prefs[priceKey] ?: "—"
val change = prefs[changeKey] ?: "0.0"
provideContent {
Column(
modifier = GlanceModifier.fillMaxSize().background(Color.DarkGray).padding(12.dp)
) {
Text("BTC", style = TextStyle(color = ColorProvider(Color.White), fontSize = 12.sp))
Text(price, style = TextStyle(color = ColorProvider(Color.White), fontSize = 20.sp))
Text("$change%", style = TextStyle(
color = ColorProvider(if (change.startsWith("-")) Color.Red else Color.Green)
))
}
}
}
}
Мінімальний інтервал оновлення через AppWidgetManager — 30 хвилин (обмеження Android). Для частіших оновлень потрібен WorkManager з PeriodicWorkRequest, але на Android 12+ фонові завдання регулюються Battery Optimizer — в Doze mode інтервали розтягуються.
Порівняння механізмів оновлення iOS та Android
| Параметр |
iOS WidgetKit |
Android Jetpack Glance |
| Мінімальний інтервал |
15-30 хвилин (регулюється системою) |
30 хвилин (WorkManager може більше) |
| Механізм оновлення |
TimelineProvider |
GlanceAppWidget + WorkManager |
| Обмеження |
Battery budget на рівні ОС |
Doze mode, Battery Optimizer |
| Рекомендація |
Для віджетів, що не потребують real-time |
Аналогічно |
Як налаштувати WidgetKit для криптовіджета? (покроково)
- Додати Widget Extension target в Xcode, увімкнути WidgetKit.
- Створити структуру
TimelineEntry з потрібними полями (ціна, зміна, дата).
- Реалізувати
TimelineProvider: методи placeholder, getSnapshot, getTimeline.
- У
getTimeline виконати запит до API, сформувати entry, вказати наступну дату оновлення.
- Створити SwiftUI View для віджета, використовуючи
Widget та StaticConfiguration.
- Налаштувати App Groups для обміну даними з основним додатком.
- Підтримати кілька розмірів через
supportedFamilies.
Типові помилки при розробці криптовіджетів
- Ігнорування бюджетів оновлень на iOS — віджет перестає оновлюватися при низькому заряді.
- Відсутність fallback UI при недоступності мережі — користувач бачить порожній віджет.
- Використання неправильного suite для App Groups — дані не передаються.
- Занадто часті оновлення на Android — конфлікт з Battery Optimizer.
Що входить у роботу
- iOS: WidgetKit extension,
TimelineProvider, SwiftUI-верстка, App Groups для shared data.
- Android: Jetpack Glance widget, WorkManager для оновлень.
- Інтеграція з API курсів (CoinGecko, Binance, CoinMarketCap або власний бекенд).
- Підтримка кількох розмірів віджета.
- Deep link з віджета в потрібний екран додатку.
- Тестування поведінки при відсутності мережі та застарілих даних.
- Гарантія на працездатність в App Store та Google Play (дотримання гайдлайнів).
Приклад робочого процесу (наш кейс)
На одному з проєктів — криптогаманець з портфелем — ми реалізували віджет для iOS. Запит клієнта: оновлення кожні 5 хвилин. Довелося використовувати комбінацію WidgetKit + background task для підтримки актуальності. На Android — Glance + WorkManager з політикою 15-хвилинного інтервалу. Підсумок: користувачі в 2 рази частіше повертаються в додаток з віджета.
Терміни
3–5 днів на кожну платформу. Якщо потрібні обидві — 5–8 днів сумарно з урахуванням спільної логіки отримання даних. Вартість розраховується індивідуально — напишіть нам, і ми оцінимо ваш проєкт за 1 робочий день. Якщо вам потрібен віджет для криптовалютного додатку, отримайте консультацію нашої команди.
Чому нативна розробка iOS — найкращий вибір для складних додатків
Додаток крашиться на cold start — EXC_BAD_ACCESS в момент ініціалізації синглтона, який звертається до іншого синглтона, який ще не ініціалізований. Або: ViewController витікає в пам'яті, тому що closure захоплює self без [weak self], і цей ViewController висить у пам'яті через два переходи після того, як користувач його покинув. Це не гіпотетичні сценарії — це два найпоширеніші класи проблем на iOS-проектах, які приходять до нас після іншої команди.
Ми займаємося iOS-розробкою понад 6 років, реалізували 50+ проєктів — від стартапів до enterprise-рішень з мільйонами користувачів. Кожен проєкт проходить через 3 етапи Code Review, власний набір UI-тестів (в середньому 150+ тест-кейсів) та обов'язковий прогін через Xcode Instruments до релізу. Нативна iOS-розробка на Swift — це прямий доступ до платформи. Без прошарку, без компромісів щодо продуктивності, з повним контролем над тим, що відбувається на кожному кадрі.
Чому нативна розробка iOS на Swift — вибір для enterprise-додатків?
Нативний код дає гарантію сумісності з новими API Apple у день їх виходу, а не через місяці адаптації в кроссплатформних фреймворках. Для додатків із чутливою до затримок логікою (фінансові термінали, медичні монітори, AR-навігація) це критично. Swift з ARC та строгою типізацією дозволяє тримати crash-free rate на рівні 99.9% за правильної архітектури. На практиці середній показник на наших проєктах — 99.8%, що на 15% вище середнього по ринку.
Як вибрати між SwiftUI та UIKit для нативної розробки iOS?
На сьогодні SwiftUI покриває переважну більшість production-завдань. Але UIKit не застарів і не зникне — Apple не deprecate-ить його, а продовжує додавати API. Реальна картина на великих проєктах: гібридний підхід. SwiftUI для більшості екранів, UIKit там, де SwiftUI впирається в обмеження.
Які сценарії SwiftUI виграє беззаперечно
Декларативний синтаксис SwiftUI скорочує код UI у 3–5 разів порівняно з UIKit. Екран налаштувань з List, Toggle, Picker — це 40 рядків SwiftUI проти 200 рядків UIKit з делегатами UITableViewDataSource. Економія часу на UI-розробку досягає 60%. Apple рекомендує починати нові проєкти на SwiftUI (Human Interface Guidelines).
@State, @Binding, @ObservableObject (а з iOS 17 — макрос @Observable) створюють реактивний зв'язок між даними та UI без ручного reloadData(). Зміна @State-змінної автоматично перемальовує порушену частину ієрархії. Це працює правильно, якщо розуміти, як SwiftUI обчислює diff — через Equatable та id у ForEach.
AsyncImage, NavigationStack з типобезпечним роутингом через NavigationPath, searchable, refreshable — це готові патерни, які UIKit вимагає реалізовувати вручну.
Коли UIKit залишається необхідним
UICollectionView з compositional layout та diffable data source — складні сітки з різними типами комірок, горизонтальними секціями всередині вертикального скролу, динамічними розмірами комірок. SwiftUI LazyVGrid / LazyHGrid не дають такого контролю.
Кастомні переходи між екранами. UIViewControllerAnimatedTransitioning та UIViewControllerInteractiveTransitioning — інтерактивний pop gesture з частковим прогресом, кастомний hero-перехід з точним керуванням frame. SwiftUI matchedGeometryEffect покриває частину випадків, але не всі.
UITextView з TextKit 2. Багатий редактор тексту, кастомні атрибути, кастомний рендеринг — TextKit 2 (доступний з iOS 16) перейшов на async layout, що вирішило проблеми з продуктивністю на довгих документах. SwiftUI TextEditor — це обгортка навколо UITextView без прямого доступу до TextKit.
UIScrollView з кастомною поведінкою. scrollViewDidScroll, parallax-ефекти, sticky headers з кастомною логікою, pull-to-refresh з кастомним індикатором. SwiftUI ScrollView з scrollPosition та onScrollGeometryChange (iOS 17) закриває частину випадків, але не всі.
Як ми інтегруємо SwiftUI та UIKit: крок за кроком
- Ідентифікуємо екрани, де SwiftUI дає максимальний виграш (списки, форми, налаштування) — зазвичай 70-80% екранів.
- Для критичних до продуктивності ділянок (складні колекції, кастомні анімації) залишаємо UIKit.
- Використовуємо UIHostingController для вбудовування SwiftUI-в'ю в UIKit navigation stack.
- Для зворотної сумісності обгортаємо UIKit-компоненти через UIViewRepresentable.
- Coordinator pattern (UIKit) керує навігацією на рівні флоу, екрани реалізовані на SwiftUI.
Один патерн, який ми використовуємо на проєктах: UIKit-координатор керує навігацією, а самі екрани на SwiftUI. Координатор створює UIHostingController, передає ViewModel через ініціалізатор або @EnvironmentObject, керує переходами. Це дає чисте розділення: SwiftUI займається UI, Coordinator — навігацією. Завдяки такому підходу ми скорочуємо час дебагу на 30% порівняно з чистим UIKit.
Як async/await та Combine працюють разом?
До Swift 5.5 асинхронний код на iOS будувався на Combine або callback-ланцюжках. З появою async/await та Actor модель конкурентності стала частиною мови. На нових проєктах ми використовуємо async/await як основний інструмент для мережевих викликів та бізнес-логіки, Combine — для реактивної прив'язки UI-стану.
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetch(id: id)
} catch {
// обробити помилку
}
}
}
Combine залишається незамінним для дебаунсингу введення, об'єднання кількох Publishers (CombineLatest, Zip) та функціональної обробки потоку значень (map, flatMap, filter). На практиці 80% проєктів використовують обидва підходи, обираючи інструмент під задачу. Це дозволяє підвищити швидкість розробки на 20% без втрати якості.
Архітектура iOS-додатку
MVVM — базовий патерн. ViewModel містить логіку та @Published-стан, SwiftUI View підписується через @ObservedObject або @StateObject. Правило одне: View не знає про URLSession, CoreData, UserDefaults.
Clean Architecture додає шари Repository та UseCase. UserRepository абстрагує джерело даних (мережа vs кеш). FetchUserUseCase містить бізнес-правило. UserViewModel викликає UseCase та керує UI-станом.
TCA (The Composable Architecture) — більш строгий патерн від Point-Free. State, Action, Reducer, Effect — все явне, все тестоване, composable через Scope. Добре працює у великих командах (5+ iOS-розробників), де важлива передбачуваність поведінки.
Що входить у розробку iOS-додатку
| Етап |
Результати |
| Аналіз та проектування |
Технічне завдання, архітектурна схема, вибір стеків |
| Розробка |
Код з дотриманням App Store Review Guidelines, інтеграція з бекендом (REST/GraphQL) |
| Тестування |
Unit-тести (XCTest, покриття >75%), UI-тести (XCUITest, 150+ сценаріїв), навантажувальне тестування через Firebase Test Lab |
| Публікація |
Оформлення облікового запису розробника, підпис коду, відправка в App Store Connect |
| Підтримка |
Гарантія 30 днів після релізу, оновлення під нові версії iOS |
Інструменти, без яких не обходиться жоден реліз
Xcode Instruments. Time Profiler показує, де CPU витрачає час. Allocations — витоки пам'яті та excessive allocations. Leaks — об'єкти, які не звільняються. Перед кожним релізом — обов'язковий прогін.
Firebase Crashlytics. Crash-free rate, групування по stack trace, breadcrumbs подій до крашу. Налаштовується за 30 хвилин, дає картину по всьому парку пристроїв. В середньому crash-free rate на наших проєктах — 99.8%.
Fastlane match. Керування сертифікатами та provisioning profiles через зашифрований git-репозиторій. Усуває «у мене локально збирається, а на CI ні» раз і назавжди. Економить до 4 годин на кожну збірку при ручному підписуванні.
XCTest + XCUITest. Unit-тести для ViewModel та UseCase, UI-тести для критичних флоу (онбординг, оплата, авторизація). В середньому код покритий на 75%.
Типові помилки на iOS-проектах та їх рішення
| Проблема |
Рішення |
Витік пам'яті через захоплення self у замиканні |
Використовувати [weak self] у всіх хендлерах, де self не повинен жити довше замикання |
| Конфлікти Provisioning Profiles |
Налаштувати Fastlane match та зберігати сертифікати в окремому репозиторії |
| Повільний старт додатку через синхронну ініціалізацію синглтонів |
Перенести ініціалізацію на перший виклик або використовувати lazy var |
| Відмова App Store через невідповідність Section 4.2 (мінімальна функціональність) |
Провести попередній аудит за чек-листом App Store Review Guidelines |
Процес і терміни
| Складність |
Орієнтовний термін |
| MVP (5–8 екранів, базовий API) |
6–10 тижнів |
| Середній додаток (15–25 екранів) |
3–5 місяців |
| Складний (платежі, AR, CoreML, кастомний UI) |
5–9 місяців |
Замовте розробку під ключ — ми оцінимо ваш проєкт за 2 робочі дні та запропонуємо оптимальну архітектуру. Зв'яжіться з нами, щоб обговорити вашу задачу: гарантуємо якість коду, дотримання App Store Review Guidelines та досвід роботи з проєктами будь-якого масштабу. Отримайте консультацію — ми допоможемо вибрати правильний стек та уникнути типових помилок на старті.