Обновлено: .
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 проверяйте availability именно символа и статус релиза 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 и тесты проекта.