Live Activity для отслеживания цены криптовалюты на iOS
Live Activities появились в iOS 16.1 и дают принципиально иной способ показывать актуальные данные — не просто виджет на Home Screen, а постоянно обновляемая плашка на Lock Screen и в Dynamic Island (iPhone 14 Pro и новее). Для трейдинг-приложений и криптокошельков это точка входа, которую пользователь видит, не открывая приложение.
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
}
Запуск и обновление
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))
}
}
Что входит в работу
- Создание 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 дня. Стоимость рассчитывается индивидуально после анализа требований.







