Оновлено: .
Dynamic Island корисний, коли відповідає на коротке запитання: коли приїде машина, скільки залишилося до кінця таймера, що відбувається з тренуванням. Щоб зробити таку функцію надійною, потрібні не лише компактний макет, але й ActivityKit, widget extension, коректні оновлення, обробка дій та зрозуміле завершення. Тут повний шлях від продуктового сценарію до перевірки реалізації.
Людина замовила їжу й відкрила робоче листування. У верхній частині екрана їй достатньо побачити «4 хв». Після розкриття — зрозуміти, що кур’єр біля входу з двору. На екрані блокування — впізнати те саме замовлення. Після доставки статус має завершитися. Саме цей ланцюжок ми проєктуємо: один процес, кілька подань і жодної обіцянки, якої дані вже не підтверджують.
Перевірено за документацією Apple 5 листопада 2026 року. Це практичний посібник та авторські схеми, а не звіт про тестування всіх iPhone. Приклади Swift показують окремі частини інтеграції; вони не є зібраним та підписаним застосунком. Доступність кожного API потрібно звіряти з Xcode SDK та deployment target проєкту.
1. Dynamic Island, Live Activity та віджет — три різні поняття
Dynamic Island — місце відображення. Live Activity — відображення поточної активності. ActivityKit — механізм управління її життям. Інтерфейс створюють за допомогою WidgetKit та SwiftUI у розширенні програми. Звичайний віджет, хоч і використовує той же WidgetKit, оновлюється за іншим контрактом: його timeline не можна підставити замість життєвого циклу Live Activity. Ця відмінність закріплена в посібнику Apple з інтеграції Live Activities.


| Механізм | За що відповідає | Приклад |
|---|---|---|
| ActivityKit | Створити, оновити, завершити активність | Доставка від початку відстеження до фіналу |
| WidgetKit + SwiftUI | Описати подання та підтримувані взаємодії | Короткий ETA та розширена картка |
| Dynamic Island | Системна область показу на підтримуваному пристрої | Компактний, мінімальний, розкритий стан |
| Звичайний віджет | Регулярна інформація та швидкий вхід | План на день, найближча подія |
Звідси важливий наслідок: підтримка iPhone без острівця не є марною роботою. Екран блокування залишається самостійним контекстом. І навпаки: малюнок капсули всередині програми не підключає продукт до системного інтерфейсу.
2. Спочатку домовтеся, що саме людина відстежує
Для доставки задайте момент початку спостереження, зміну етапів та фінал. "Замовлення прийняте" може ще не вимагати постійної уваги. «Кур'єр у дорозі» вже відповідає на питання, що повторюється. «Кур'єр чекає» потребує помітної зміни. «Доставлено» завершує процес; картка не повинна перетворюватися на пропозицію замовити ще.
Для тренування головне питання інше: чи триває запис, скільки часу минуло і як поставити його на паузу. Для матчу — рахунок та поточний період. Для таймера — час, що залишився. Це різні моделі вмісту, навіть якщо всі вони містяться у схожій формі.
AI-генерація теж не отримує автоматичного права на острівець. Якщо результат можна спокійно отримати пізніше, повідомлення про готовність та екран історії зазвичай вирішують завдання. Якщо користувач усвідомлено стежить за короткою поточною роботою, оцініть live-подання за його реальною корисністю. Ніколи не показуйте вигаданий відсоток заради гарної анімації.
Перед макетом запишіть одну фразу: «Поки відбувається процес, людина хоче знати відповідь та інколи виконати дію; подання зникає за умови». Якщо остання частина відсутня, швидше за все, вибрано не той механізм.
3. Чотири основні макети та один інформаційний пріоритет
У компактному стані залиште головний показник. Minimal повинен залишатися впізнаваним у сусідстві з іншою активністю. Expanded може додавати контекст та дію. На екрані блокування потрібна достатня інформація, щоб зрозуміти завдання без відкриття програми. Розміри, зони та правила розкриття задає система; їх слід звіряти з Human Interface Guidelines для Live Activities.

Наприклад: компактно — «4 хв»; мінімально — символ доставки; розкрито — "Кур'єр біля входу з двору"; на блокуванні — етап та зв'язок із конкретним замовленням. Не зменшуйте один великий екран до чотирьох розмірів. Вибирайте інформацію для кожного контексту.
Враховуйте обмежену ширину острівця, кілька одночасно активних процесів, великий текст, довгі переклади та VoiceOver. Число "4" без доступного імені неоднозначне. Підпис «Кур'єр прибуде приблизно за чотири хвилини» повідомляє зміст. Секретні деталі замовлення не повинні ставати видимими лише тому, що телефон лежить на столі.
4. Підготовка проєкту та модель даних
Почніть з існуючого iOS target. Додайте або використовуйте Widget Extension, увімкніть конфігурацію Live Activity до набору віджетів розширення та встановіть NSSupportsLiveActivities = YES у застосунку. Перевірте підпис, ідентифікатори, цільові версії та доступність загального типу даних для програми та розширення. Сайт або JavaScript-обгортка самі собою такий target не створюють.
ActivityAttributes розділяє постійні атрибути та змінний ContentState. Номер замовлення залишається незмінним; етап та прогноз прибуття змінюються. У мінімальному прикладі не передаємо адресу, персональні відомості, зображення та повну серверну модель:
import ActivityKit
struct DeliveryAttributes: ActivityAttributes {
struct ContentState: Codable, Hashable {
var phase: String
var etaMinutes: Int?
}
let orderID: String
}
etaMinutes може бути невідомим. Значення nil тут чесніше за нуль: «немає прогнозу» не означає «кур'єр вже приїхав». У робочому застосунку етапи краще описувати узгодженим набором значень, щоб backend та клієнт не розходилися у сенсі.
Перевіряйте ActivityAuthorizationInfo().areActivitiesEnabled. Налаштування звичайних повідомлень та дозвіл Live Activities не взаємозамінні. Зміни можна спостерігати через activityEnablementUpdates; це описано в API налаштувань ActivityKit. Навіть після позитивної перевірки створення може завершитися помилкою, наприклад, через системні обмеження.
enum DeliveryActivityError: Error { case disabled }
@available(iOS 16.2, *)
func startDelivery(orderID: String) throws
-> Activity<DeliveryAttributes> {
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
throw DeliveryActivityError.disabled
}
let state = DeliveryAttributes.ContentState(
phase: "on_the_way", etaMinutes: 4
)
return try Activity<DeliveryAttributes>.request(
attributes: DeliveryAttributes(orderID: orderID),
content: ActivityContent(
state: state,
staleDate: .now.addingTimeInterval(120)
),
pushType: nil
)
}
Функція навмисно повертає помилку коду, що викликає. Продукт має показати результат створення у своєму звичайному інтерфейсі. Приклад використовує ActivityContent, доступний з iOS 16.2, та локальне оновлення без push. Для серверного варіанта знадобиться окреме налаштування, описане нижче.
5. Як зв'язати дані з Dynamic Island
В ActivityConfiguration задається звичайне подання, а в DynamicIsland — компактні області та розкритий вигляд. Наступний фрагмент показує саме структуру. Подання DeliverySummary - компонент вашої програми, який потрібно реалізувати; він не є класом Apple.
ActivityConfiguration(for: DeliveryAttributes.self) { context in
DeliverySummary(state: context.state, isStale: context.isStale)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.bottom) {
DeliverySummary(state: context.state, isStale: context.isStale)
}
} compactLeading: {
Image(systemName: "shippingbox")
} compactTrailing: {
if !context.isStale, let eta = context.state.etaMinutes {
Text("\(eta) хв")
} else {
Text("—")
}
} minimal: {
Image(systemName: "shippingbox")
}
}
У реальному коді DeliverySummary повинен явно обробляти isStale та відсутність ETA. Для переходу додайте widgetURL, що веде до потрібного замовлення. Після відкриття перевірте поточний обліковий запис та доступ до об'єкта: публічне посилання не є авторизацією. Не відправляйте людину на головний екран шукати те замовлення, на яке вона щойно натиснула.
6. Кнопки: App Intents та підтверджений результат
Для підтримуваних інтерактивних подань використовують Button(intent:) та Toggle з App Intent. Потрібний intent звертається до тієї ж команди предметної області, як і основний екран. Apple пояснює контекст виконання в інструкції з інтерактивних віджетів та Live Activities; для операцій з активністю передбачено LiveActivityIntent.
Для паузи тренування ланцюжок такий: натискання → перевірка поточної сесії → команда призупинити запис → підтвердження → новий стан. Якщо команда надходить на інший пристрій або сервер, це не миттєва локальна зміна напису. Повторне натискання не повинно випадково відновити тренування: команда «встановити паузу» надійніша за сліпе «перемкнути стан».
Для доставки зазвичай достатньо відкрити деталі або зв'язатися з кур'єром. Скасування замовлення, відкриття замку та інші чутливі дії вимагають власного сценарію підтвердження та системної автентифікації там, де вона потрібна. Невелика кнопка не зменшує наслідків операції.
7. Серверні оновлення: три токени, які не можна плутати
Коли дані надходять із сервера, підключіть Push Notifications capability і створюйте активність з pushType: .token. Потім спостерігайте activity.pushTokenUpdates: токен з'являється асинхронно та може змінитися. Backend пов'язує його з конкретною активністю та обліковим записом. Не зберігайте його у відкритих логах.
| Токен | Призначення | Помилка, якої слід уникати |
|---|---|---|
| Звичайний device token APNs | Звичайні віддалені сповіщення програми | Використовувати його замість ActivityKit update token |
| Update token активності | Оновити або завершити конкретну Live Activity | Прив'язати один токен до всіх замовлень |
| Push-to-start token | Віддалено розпочати нову активність на підтримуваному runtime | Надіслати туди звичайне оновлення або ігнорувати ротацію |
Для оновлення конкретної активності запит APNs використовує apns-push-type: liveactivity та topic виду <app-bundle-id>.push-type.liveactivity. Вміст content-state має відповідати Swift-моделі. Ось авторський приклад формування оновлення на JavaScript:
const now = Math.floor(Date.now() / 1000);
const payload = {
aps: {
timestamp: now,
event: "update",
"content-state": { phase: "on_the_way", etaMinutes: 4 },
"stale-date": now + 120
}
};
Тут 120 секунд — вибране для навчального замовлення вікно актуальності, а не ліміт Apple. На сервері час та термін достовірності необхідно брати з логіки продукту. Значення часу у конверті APNs використовують Unix seconds; це не привід кодувати будь-який Swift Date всередині власної моделі за тією ж схемою. Перевіряйте реальне декодування.
Повний контракт start/update/end, пріоритетів та каналів описаний у документації ActivityKit push. Для загальної спортивної події можливий broadcast-канал на версіях, що підтримуються; персональні замовлення не можна бездумно поєднувати в загальну аудиторію. Frequent Updates — окреме налаштування та бюджет, а не обіцянка оновлення кожну секунду. Прийняття запиту APNs не доводить ні відображення, ні перегляд.
8. Актуальність, завершення та реальні обмеження
Локальна підтверджена зміна передається існуючому об'єкта активності через update. У цьому прикладі нульовий ETA допустимий, бо відомо: кур'єр уже чекає, а не тому, що прогноз загублений.
let nextState = DeliveryAttributes.ContentState(
phase: "courier_waiting", etaMinutes: 0
)
await activity.update(
ActivityContent(
state: nextState,
staleDate: .now.addingTimeInterval(120)
)
)
Достовірність відповіді важливіша за її безперервну присутність. Якщо оновлення ETA не надійшло, не продовжуйте впевнено обіцяти попередні чотири хвилини. staleDate відзначає момент старіння; context.isStale дозволяє змінити UI. Саме поле не пише текст «Дані застаріли» за розробника та не оновлює прогноз.
Розділяйте чотири речі: замовлення ще виконується; дані застаріли; картка прихована; команда користувача не підтверджена. Вони можуть поєднуватися. Видалення Live Activity з блокування не скасовує доставку. Після перезапуску програми звіртеся з Activity<DeliveryAttributes>.activities, а не створюйте нову копію кожного завдання.
Для стандартних Live Activities поточна документація вказує до восьми годин активного життя, потім до чотирьох додаткових годин на екрані блокування та сумарний бюджет 4 KB на статичні та динамічні дані. Rendering-розширення не отримує довільного мережевого доступу або оновлення геопозиції. Ці обмеження тривалості та даних потрібно враховувати до вибору сценарію.
let finalState = DeliveryAttributes.ContentState(
phase: "delivered", etaMinutes: nil
)
await activity.end(
ActivityContent(state: finalState, staleDate: nil),
dismissalPolicy: .default
)
По завершенні передайте фінальний контент. Політика .default залишає системі можливість деякий час показувати результат на блокуванні; .immediate прибирає його відразу; .after(...) задає обмежену відстрочку. Вибір має бути осмисленим: людині може знадобитися побачити фінальний статус, але нескінченна картка вже закінченої роботи створює хибне очікування.
9. Apple Watch, StandBy, Mac та CarPlay
Острівець — не єдина точка використання. Для Apple Watch і CarPlay можна підготувати мале подання через supplementalActivityFamilies([.small]) і вибирати вміст за activityFamily. Посібник із додаткових подань також описує адаптацію до інших розмірів та обмеженої ширини Dynamic Island.

Найважливіша відмінність: інтерактивність на Apple Watch не означає інтерактивність у CarPlay. Система вимикає кнопки та перемикачі Live Activity в автомобілі. Тому фраза «Кур'єр чекає» має залишатися корисною без кнопки поряд. Отримання Live Activity на годиннику або в меню Mac також не створює автономну watchOS- або macOS-програму.
StandBy вимагає читання з відстані; довгий абзац тут гірший від великого значення та зрозумілого етапу. На годиннику потрібні короткі підписи та зручні цілі натискання. На Mac враховуйте перехід до завдання та залежність від сполученого iPhone. Підтримку перевіряють за конкретним зв'язуванням версій і налаштувань.
В актуальній документації є і заплановані, і transient-активності. Transient призначена для короткої взаємодії у розкритому острівці та закінчується при виході з цього контексту; її не можна замінювати простим фоновим відстеженням. Перед використанням нових перевантажень request перевіряйте наявність саме символу та статус релізу SDK, а не тільки назву сімейства ActivityKit.
10. Перевірка, яка відрізняє прототип від інтеграції
| Умова | Очікуваний результат |
|---|---|
| Live Activities вимкнено | Основне завдання доступне; система не обіцяє острівець |
| Немає Dynamic Island | Перевірено потрібне подання блокування |
| Дві активності | Зрозуміла ідентичність кожної, немає переплутаних переходів |
| Дані застаріли | ETA не виглядає підтвердженим; видно втрату актуальності |
| Користувач видалив картку | Замовлення не скасовано; немає примусового відновлення |
| Мережа повернулася після фіналу | Стара подія не повертає завершене завдання |
| Токен змінився / APNs повернув помилку | Backend оновив реєстрацію та розрізняє доставку та прийняття запиту |
| Активність закінчилася | Показано фінал, оновлення зупинено, застосована dismissal policy |
Додайте окремі перевірки довгих локалізацій, великого тексту, VoiceOver, темного/тьмяного відображення та переходу до програми після зміни облікового запису. Для паузи або скасування перевірте повтор команди та одночасну дію з іншого пристрою.
Симулятор корисний для макетів, але не підтверджує доставку APNs, поведінку конкретного iPhone та передачу на годинник. У звіті поділяйте: зібралося; показано у симуляторі; перевірено на пристрої; перевірено із сервером. Force-quit тестуйте окремо від звичайного згортання.
11. Що має вийти у команди
Мінімальний завершений результат — один обґрунтований сценарій, узгоджена модель стану, розширення з потрібними поданнями, запуск/оновлення/завершення, точне посилання на завдання та перевірений шлях при недоступності Live Activities. Серверний транспорт та інтерактивність додаються тоді, коли вони дійсно потрібні сценарію.
Приклад критерію приймання: «Після початку поїздки людина бачить актуальне прибуття; при втраті даних інтерфейс перестає обіцяти точний ETA; після натискання відкривається правильна поїздка; після завершення оновлення припиняються; видалена користувачем активність не виникає знову заради уваги». Це набагато сильніше за вимогу «зробити красивий острів».
Застосувати посібник з Codex та Claude Code
За результатами цього дослідження ми створили відкриті скіл Interaction Surfaces для Codex і Claude Code та скіл Adaptive Interfaces. Вони допомагають проаналізувати наявний сайт або застосунок, визначити доступні API й запропонувати корисні сценарії: віджети, поточні активності, годинники, додаткові екрани та адаптацію інтерфейсу. Ви вибираєте, що впровадити, відкласти чи виключити; потім агент реалізує вибрані зміни й перевіряє результат у межах доступних платформ і пристроїв.
Для роботи над своїм застосунком можна встановити скіл Interaction Surfaces з GitHub. У ньому є налаштування ActivityKit та WidgetKit, Swift-приклад, App Intents, APNs та окремий посібник з Apple Watch. Англійський пакет охоплює усю серію; інструкція зі встановлення та приклад виклику знаходяться у загальному огляді.
Часті запитання
Чи можна запустити Dynamic Island зі звичайного сайту?
Сайт може запропонувати повідомлення або посилання на завдання, але прямого доступу до ActivityKit він не має. Потрібна нативна інтеграція програми; інсталяція PWA не замінює widget extension.
Чи потрібний окремий сервер для кожного таймера?
Ні. Для відомого локального інтервалу спочатку розгляньте системне відображення часу. Сервер потрібен там, де джерело змін віддалене; щосекундні push для руху цифр не вирішують продуктове завдання.
Чи можна гарантувати, що активність буде в острівці?
Ні. Налаштування, пристрій, інші активності та рішення системи впливають на відображення. Проєктуйте весь шлях завдання, а не залежність від однієї видимої капсули.
Чи підходить код із статті для вставки без змін?
Ні. Він пояснює межі компонентів. Для випуску потрібні реальні моделі, подання, обробка помилок, підпис, маршрутизація, перевірка API та тести проєкту.