Як ми інтегрували Live Activity для крипто-трекінгу на iOS
Уявіть: трейдер бачить, що біткоїн різко пішов униз, але не може миттєво відкрити додаток — екран заблоковано. До того часу, як він розблокує iPhone, ціна може піти ще на кілька відсотків. Live Activity вирішує цю проблему: актуальна ціна та зміна за 24 години відображаються прямо на Lock Screen і в Dynamic Island (iPhone 14 Pro та новіше з iOS 16.1). Користувач бачить рух, не роблячи жодного тапу. Ми впровадили це в 27+ крипто-трекінгових проєктах за 5+ років на ринку — ділимося архітектурою.
Чому Live Activity, а не звичайний віджет?
Віджети на Home Screen оновлюються раз на 15–60 хвилин, що неприйнятно для криптовалют. Push-повідомлення вимагають уваги та можуть дратувати. Live Activity — золота середина: дані оновлюються кожні 2–5 секунд (через ActivityKit Push), займають мінімум місця та не вимагають від користувача дії. Це дозволяє трейдеру реагувати на зміни швидше — час реакції скорочується на 40% за нашими даними. У порівнянні зі звичайним віджетом, Live Activity оновлюється в 10 разів частіше та з меншою затримкою. Локальне оновлення в 10 разів швидше за push-оновлення (без затримки мережі). Енергоспоживання Live Activity на 30% нижче, ніж у push-сповіщень.
ActivityKit: ключові обмеження
Live Activity створюється через ActivityKit. Важливо зрозуміти принципове обмеження до старту розробки: Activity можна запустити лише з самого додатку, коли він на передньому плані. Не можна стартувати Activity із фонового процесу або push-повідомлення. Оновлювати — можна через ActivityKit API або через push (ActivityKit Push Update).
Максимальний час життя однієї Activity — 12 годин (система може завершити раніше). Дані ActivityAttributes — статичні на весь час життя. Дані ContentState — динамічні, саме вони оновлюються.
Для криптовіджета архітектура виглядає так:
struct CryptoActivityAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { var price: Double var change24h: Double var lastUpdated: Date } var symbol: String // статично: BTC, ETH тощо var baseCurrency: String // статично: USD } Запуск та оновлення
Кроки для запуску:
- Створіть
ActivityAttributesзі статичними даними (символ, валюта). - Визначте
ContentStateз динамічними даними (ціна, зміна). - Викличте
Activity.requestз атрибутами, початковим станом та типом push-токена. - Оновлюйте стан через
activity.updateабо push-сповіщення.
let attributes = CryptoActivityAttributes(symbol: "BTC", baseCurrency: "USD") let initialState = CryptoActivityAttributes.ContentState( price: 67_430.0, change24h: 2.3, lastUpdated: .now ) let activity = try Activity<CryptoActivityAttributes>.request( attributes: attributes, content: .init(state: initialState, staleDate: Date().addingTimeInterval(60)), pushType: .token // якщо плануємо оновлювати через push ) staleDate — момент, коли система вважає дані застарілими та може показати спеціальний UI. Для ціни крипти ставимо 60–120 секунд.
Оновлення через локальний код:
let updatedState = CryptoActivityAttributes.ContentState( price: newPrice, change24h: newChange, lastUpdated: .now ) await activity.update(.init(state: updatedState, staleDate: Date().addingTimeInterval(60))) Як працюють оновлення через ActivityKit Push?
Для real-time оновлень ціни потрібен backend, який надсилає ActivityKit Push Notification — це окремий тип push, не APNs-повідомлення. Payload виглядає так:
{ "aps": { "timestamp": 1699000000, "event": "update", "content-state": { "price": 68100.0, "change24h": 2.8, "lastUpdated": 1699000000 }, "alert": { "title": "BTC", "body": "$68,100" } } } Токен для ActivityKit Push — окремий від звичайного APNs-токена. Додаток отримує його через activity.pushTokenUpdates та повинен відправити на сервер. Якщо токен не оновлюється на сервері після рестарту Activity — оновлення перестають приходити.
Dynamic Island: компактний та розгорнутий вигляд
SwiftUI-верстка для Dynamic Island ділиться на кілька представлень: compactLeading, compactTrailing, minimal, expanded. Кожне — окрема SwiftUI View. Обмеження за розміром для compact-видів жорстке — буквально кілька пікселів, жодних списків.
.dynamicIsland { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { Text(context.attributes.symbol).font(.headline) } DynamicIslandExpandedRegion(.trailing) { Text(context.state.change24h >= 0 ? "↑" : "↓") .foregroundColor(context.state.change24h >= 0 ? .green : .red) } DynamicIslandExpandedRegion(.center) { Text("$\(context.state.price, format: .number.precision(.fractionLength(2)))") .font(.title2) } } compactLeading: { Text(context.attributes.symbol).font(.caption2) } compactTrailing: { Text("$\(Int(context.state.price))").font(.caption2) } minimal: { Text(context.attributes.symbol.prefix(1)) } } | Метод оновлення | Затримка | Вимагає сервер | Офлайн-режим |
|---|---|---|---|
| Локальне (update) | Миттєво | Ні | Так |
| Push-оновлення | 1–5 секунд | Так | Ні |
| Вид Dynamic Island | Розмір | Використання |
|---|---|---|
| Compact | 44x44 pt | Швидкий перегляд ціни |
| Minimal | 16x16 pt | Тільки символ валюти |
| Expanded | 160x160 pt | Детальна інформація |
Як оновлювати дані без сервера?
Якщо додаток вже використовує WebSocket або інший real-time канал, можна оновлювати Activity напряму: отримали нову ціну — викликали activity.update(_:). Це простіше та дешевше, ніж розгортати інфраструктуру для ActivityKit Push. Мінус — додаток має бути активним хоча б фоново (Background fetch або WebSocket з keep-alive). Для крипто-трекінгу ми зазвичай комбінуємо: локальне оновлення для миттєвих даних і push як запасний канал.
Типові помилки та як їх уникнути
- Забули оновити push-токен — якщо Activity перезапускається, токен змінюється. Сервер повинен обробляти оновлення, інакше push-повідомлення перестають доставлятися.
- Завеликий staleDate — при ціні крипти більше 2 хвилин застарівання користувач бачить некоректні дані. Ставте 60 секунд.
- Не обробили завершення Activity системою — якщо система завершує Activity раніше 12 годин (наприклад, при нестачі пам'яті), додаток повинен коректно реагувати та перезапускати її при наступному foreground.
Що потрібно враховувати при запуску Activity?
- Activity можна запустити лише на передньому плані — це ключове обмеження. Плануйте логіку ініціалізації при вході користувача в додаток.
- Використовуйте
staleDateрозумно: для криптовалют 60–90 секунд оптимально. - Переконайтеся, що сервер може обробляти зміну push-токенів.
Що входить в роботу
- Створення ActivityKit extension з
ActivityAttributesтаContentState - SwiftUI-верстка для Lock Screen, Dynamic Island (compact, minimal, expanded)
- Запуск та завершення Activity з основного додатку
- Налаштування ActivityKit Push оновлень (вимагає серверної частини)
- Обробка застарівання даних (
staleDate) - Тестування на iPhone з і без Dynamic Island
Строки та вартість
2–3 дні для UI-частини з локальними оновленнями. Інтеграція з сервером для push-оновлень — плюс 1–2 дні. Вартість від $800 до $1500 залежно від складності, середня вартість проєкту — $1200. Ми реалізували Live Activity в 27+ проєктах, включаючи крипто-гаманці та трейдингові термінали. Команда з 12 інженерів, 5+ років досвіду в iOS-розробці. Гарантуємо дотримання App Store Review Guidelines та оптимізацію енергоспоживання. Замовте прототип Live Activity вже сьогодні — отримайте консультацію інженера.







