Мы собрали 20 лет заметок о световой живописи в один гид. Он бесплатный. Читать гид ×
× ПриложенияТехнологииГалерея БлогО студииНаписать нам ›
Главная/Блог/Live Activities в iOS: как показать прогресс съёмки на экране блокировки
Технологии

Live Activities в iOS: как показать прогресс съёмки на экране блокировки

Андрей Морозов · 2026-11-11 · Cross-Discipline Technology

Представьте: пользователь запустил долгую экспозицию в приложении для световой живописи, свернул экран — и хочет видеть, сколько осталось, не открывая камеру заново. Это не push-уведомление: push прилетает один раз и исчезает, а здесь нужен живой виджет, который сам обновляется в реальном времени, пока идёт процесс — съёмка, рендер или экспорт. В iOS за это отвечают Live Activities поверх ActivityKit и WidgetKit.

Коротко: Live Activities — это живые виджеты на экране блокировки и в Dynamic Island, которые показывают прогресс длительного процесса без открытия приложения. Запускаются через Activity.request(), обновляются локально через Activity.update() или удалённо через APNs, требуют отдельный Widget Extension и работают начиная с iOS 16.1. Для творческого приложения это уместно там, где процесс реально идёт минуту и дольше — долгая экспозиция, рендер видео, экспорт галереи, — а не для разовых быстрых действий.

Если фича вроде живого индикатора съёмки перерастает в полноценный Android-компаньон, облачную синхронизацию или отдельную инфраструктуру под несколько платформ — на этом этапе разумно заказать разработку мобильного приложения у команды, которая ведёт такие проекты целиком, а не доращивать архитектуру силами одного iOS-разработчика.

Что такое Live Activities и чем это отличается от обычного пуша

Push-уведомление — это разовое сообщение: оно появилось, пользователь его увидел (или не увидел), и на этом его жизненный цикл закончился. Live Activity устроена иначе — это постоянный виджет, который живёт на экране блокировки весь период актуальности данных и обновляется столько раз, сколько нужно, без повторной «доставки» пользователю.

Apple представила Live Activities в iOS 16.1 вместе с фреймворком ActivityKit. Технически это SwiftUI-представление, которое рендерится не внутри вашего приложения, а в системном Widget Extension — отдельном таргете Xcode-проекта, который работает независимо от основного процесса приложения. Это ключевое архитектурное решение: даже если пользователь закрыл приложение или устройство ушло в спящий режим, виджет на блокировке продолжает обновляться.

Для творческого iOS-приложения это открывает сценарий, которого раньше просто не было: пользователь видит прогресс «вживую», не держа приложение открытым и не тратя внимание на ожидание. Это особенно ценно для процессов, которые нельзя ускорить — долгой выдержки при съёмке, тяжёлого рендера или экспорта большого файла.

С точки зрения разработчика это ещё и смена привычного мышления: раньше весь прогресс жил внутри одного экрана приложения, и если пользователь уходил в другое окно, индикатор просто переставал быть видимым. Live Activity снимает это ограничение — состояние процесса становится частью системного UI, которым управляет не ваш ViewController, а WidgetKit и его собственный жизненный цикл обновлений.

Руки разработчика на клавиатуре MacBook, на заднем плане размыто окно Xcode с кодом в тёмной студийной обстановке

Где пользователь увидит живую активность

У Live Activity три точки появления, и они не взаимоисключающие — система показывает подходящую форму в зависимости от состояния устройства.

Экран блокировки

Основное место — карточка на экране блокировки, похожая на расширенное уведомление, но без исчезающей анимации. Она занимает фиксированную область под часами и показывает всё, что вы передали в ContentState: прогресс-бар, таймер, иконку, текстовые метки.

Dynamic Island (iPhone 14 Pro+/iPhone Duo)

На устройствах с вырезом-таблеткой — начиная с iPhone 14 Pro и включая актуальную линейку вроде iPhone Duo — та же активность сворачивается в компактную или развёрнутую форму прямо в Dynamic Island. Пользователь может долгим нажатием развернуть её, не покидая текущее приложение на экране.

StandBy-режим (iOS 17+)

Если телефон лежит на боку на зарядке в режиме StandBy, та же Live Activity может отрисоваться на весь экран в увеличенном виде — Apple называет это «минимальным представлением» для режима прикроватных часов. Отдельный код для этого обычно не нужен: система берёт существующее SwiftUI-представление и масштабирует layout.

Крупный план камеры-выреза смартфона, светящейся абстрактным оранжевым кольцом прогресса, тёмный фон

Из чего состоит Live Activity — структура данных

ActivityKit жёстко разделяет данные на две категории, и это разделение определяет, что можно менять «на лету», а что нельзя.

ActivityAttributes (статические данные)

Это то, что не меняется за время жизни активности: идентификатор проекта, название сцены, которую снимает пользователь, тип процесса. Задаётся один раз при запуске и больше не трогается.

struct ExposureAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        var progress: Double
        var remainingSeconds: Int
        var stage: String
    }
    var sceneTitle: String
}

ContentState (то, что обновляется)

А вот ContentState — это как раз то, что меняется в процессе: доля выполненного прогресса, оставшееся время, текущий этап (съёмка → обработка → сохранение). Каждое обновление ContentState — это новый «кадр» виджета, который система тут же перерисовывает.

SwiftUI-представление для блокировки и Dynamic Island

В Widget Extension описывается конфигурация ActivityConfiguration, где отдельно задаются layout для экрана блокировки и три состояния Dynamic Island — свёрнутое, развёрнутое и компактное по краям. Это значит, что дизайн нужно продумать трижды: один набор данных, три формы отображения.

Как добавить Live Activity в творческое приложение — шаги реализации

Ниже — последовательность шагов для приложения, где долгий процесс уже существует (например, long-exposure съёмка или экспорт анимации), и нужно просто вывести его прогресс на блокировку.

  1. Добавить Widget Extension target. File → New → Target → Widget Extension в Xcode, с галочкой «Include Live Activity». Это отдельный бинарник со своим Info.plist и своим SwiftUI-кодом.
  2. Включить флаг в Info.plist основного приложения. Без NSSupportsLiveActivities = true система молча откажет в запуске активности — никакой ошибки компиляции не будет, просто вызов не сработает.
  3. Описать ActivityAttributes и ContentState. Структура данных, как в примере выше — общая для основного приложения и Widget Extension, обычно выносится в отдельный shared-файл или framework.
  4. Запустить активность через Activity.request() в момент старта процесса.
let attributes = ExposureAttributes(sceneTitle: "Ночной пейзаж")
let state = ExposureAttributes.ContentState(
    progress: 0.0, remainingSeconds: 30, stage: "Съёмка"
)
let activity = try Activity.request(
    attributes: attributes,
    content: .init(state: state, staleDate: nil)
)
  1. Обновлять локально через Activity.update(), если процесс идёт на устройстве. Для удалённых обновлений (например, если рендер идёт на сервере) используется push-канал ActivityKit через APNs — это отдельный тип токена, не совпадающий с обычным push-токеном приложения.
await activity.update(
    .init(state: .init(progress: 0.6, remainingSeconds: 12, stage: "Обработка"), staleDate: nil)
)
  1. Завершить активность, когда результат готов. Вызов activity.end(dismissalPolicy:) с финальным состоянием — например, показать «Готово» ещё несколько секунд перед автоматическим скрытием карточки.

Лимиты и тайминги, которые ломают UX, если их не учесть

Это тот слой, который обычно узнают только на реальном устройстве, а не в документации: восьмичасовое ограничение на жизнь активности, троттлинг частых обновлений, теснота Dynamic Island и поведение при сбоях сети.

Максимальное время жизни активности

Apple ограничивает общее время жизни активности восемью часами — после этого система принудительно завершает её, даже если процесс формально ещё не закончен. Для большинства творческих сценариев (съёмка, рендер, экспорт) это не проблема, но для фоновых многочасовых задач стоит закладывать это заранее.

Частота обновлений

Система троттлит слишком частые вызовы update(), особенно удалённые push-обновления через APNs — рекомендация Apple укладывается в несколько обновлений в минуту, а не в секунду. Если прогресс-бар должен выглядеть плавным, лучше анимировать переход между значениями средствами SwiftUI, а не слать обновление на каждый процент.

Теснота Dynamic Island

Dynamic Island показывает ограниченный набор элементов в компактной форме (обычно одна иконка слева и одно значение справа), поэтому весь богатый UI из развёрнутого состояния там просто не поместится физически. Дизайн нужно закладывать с расчётом на самую тесную форму, а не наоборот.

Поведение при сбоях сети

Если удалённые обновления идут через APNs, а соединение прерывается, Live Activity не исчезает молча — она просто перестаёт обновляться и через заданный staleDate помечается системой как устаревшая (обычно это визуально выражается приглушением цвета карточки). Поэтому в ContentState разумно всегда передавать актуальный staleDate, а не оставлять его пустым, как в упрощённом примере выше: так пользователь увидит честный сигнал «данные не свежие», а не замороженный на старом значении прогресс.

Когда это уместно творческому iOS-приложению, а когда — лишняя сложность

Live Activity оправдана там, где у процесса есть заметная длительность и пользователю важно знать его состояние, не открывая приложение. Хорошие кандидаты: съёмка light painting с длинной выдержкой, где таймер экспозиции тикает десятки секунд; рендер видео из нарисованной светом анимации; экспорт тяжёлой галереи кадров в высоком разрешении. Во всех этих случаях пользователь закономерно хочет отвлечься на другое приложение, не теряя из виду прогресс.

А вот разовые быстрые действия — сохранение одного кадра, применение фильтра, которое занимает доли секунды, — не стоят архитектурных затрат на отдельный Widget Extension. Там достаточно обычного индикатора загрузки внутри приложения. Правило простое: если процесс короче 10–15 секунд, Live Activity, скорее всего, появится и тут же исчезнет, не успев принести пользу, а ресурсы на реализацию будут потрачены впустую.

А что у Android?

У Android нет полного эквивалента Live Activities, но направление похожее: Live Updates на базе Progress-style уведомлений и элементы Material You позволяют показать похожий «живой» прогресс в шторке уведомлений и на экране блокировки некоторых устройств. Глубина интеграции при этом другая — единого Dynamic Island-аналога с компактной и развёрнутой формой на уровне системы у Android пока нет, поэтому кросс-платформенную реализацию обычно проектируют раздельно под каждую ОС.

На практике это означает, что код `ActivityAttributes`/`ContentState` с iOS-стороны не переносится на Android напрямую — там своя модель данных для Progress-уведомлений, и общий слой можно вынести разве что на уровне бизнес-логики (расчёт прогресса, статус процесса), а не на уровне UI-реализации.

Частые вопросы

Работает ли Live Activity без интернета?
Да, если обновления идут локально через Activity.update() — весь процесс, включая съёмку и рендер на устройстве, не требует сети. Сеть нужна только для удалённых push-обновлений через APNs.

Нужен ли сервер для обновления активности?
Нет, если данные меняются на самом устройстве. Сервер понадобится только в сценарии, где процесс выполняется удалённо (например, облачный рендер) и статус нужно прислать обратно в приложение через APNs.

Можно ли показать несколько Live Activities одновременно?
Технически да — iOS поддерживает несколько параллельных активностей одного или разных типов, но Dynamic Island физически показывает подробно только одну за раз, остальные сворачиваются в список на блокировке.

Разряжает ли это батарею?
Сама по себе статичная карточка почти не расходует заряд. Расход растёт только при частых обновлениях и активном рендере на устройстве — то есть виноват сам процесс (съёмка, экспорт), а не механизм Live Activity.

Поддерживают ли Live Activities старые iPhone?
Начиная с iOS 16.1 — да, на любом устройстве, которое обновилось до этой версии, но без Dynamic Island: там карточка будет видна только на экране блокировки, без компактной формы в вырезе.

В чём разница между Live Activity и обычным виджетом на главном экране?
Обычный виджет на главном экране обновляется по расписанию системы (раз в несколько минут) и не привязан к конкретному активному процессу. Live Activity создаётся явно под один запущенный процесс, обновляется в реальном времени и автоматически исчезает, когда процесс завершён.

Live Activities — не самая сложная технология в арсенале iOS-разработчика, но она требует отдельного таргета, продуманной структуры данных и дисциплины с лимитами обновлений. Если такая фича в вашем приложении начинает обрастать серверной частью, аналитикой и Android-версией — это уже не доработка одного экрана, а повод привлечь команду, которая умеет вести мобильный проект целиком.

Есть вопрос по теме?

Напишите нам — с удовольствием разберём ваш случай и, может быть, сделаем из него следующую статью.

xdt_software@oodot.com ›