Live Activities появились начиная с версии IOS 16.1 на всех моделях iPhone. Live Activity отображают самые последние данные вашего приложения на экране блокировки iPhone и в Dynamic Island. Это позволяет вашим пользователям видеть самую актуальную информацию которая происходит в вашем приложении.
Важно понимать что и Dynamic Island и Live Activities сами по себе являются частью ActivityKit фреймворка, поэтому для простоты, дальше в посте я буду называть Dynamic Island и Live Activities просто - Live Activities.
Note1: Live Activity доступен только на iPhone.
Note2: Во время написания поста Dynamic Island доступен только на моделях iPhone 14 Pro и Pro Max.
TLDR; Добавление Live Activities
Live Activities являются частью Widget Extension, однако если вы не хотите добавлять виджеты в приложение, то делать это не обязательно.
Для того чтобы ваше приложение начало поддерживать Live Activity вы должны выполнить следующее:
Добавить Widget Extension в проект и выбрать галочку “Include Live Activity”. Это создаст все дефолтные файлы необходимые для работы.
Добавить поле
NSSupportsLiveActivities
вInfo.plist
и выставить ему значениеYES
Добавить код который определит
ActivityAttributes
для вашей activity. Activity Attributes это модель данных которая будет использоваться вашей Live Activity.Создать
ActivityConfiguration
которая будет приниматьActivityAttributes
который мы определили шагом ранее и говорить какие вьюшки мы хотим отобразить в Dynamic Island и Live Activity.Добавить код который будет стартовать, обновлять, удалять activity.
Добавление статических и динамических данных
Для того чтобы отображать данные, мы должны их передать в наш активити. Для этого нам нужно создать объект который будет реализовывать ActivityAttributes
протокол.
import ActivityKit
struct TimerAttributes: ActivityAttributes {
public typealias TimerStatus = ContentState
public struct ContentState: Codable, Hashable {
var timerRange: Date
}
var timerName: String
}
ContentState - это все те данные которые будут обновляться динамически. Live Activity будет запрашивать их для каждого обновления. Все остальные переменные которые объявлены за пределами ContentState будут статическими и будут нужны только один раз, когда мы создаем наш активити.
Note: Динамические данные которые кодируются в Live Activity не могут превышать 4KB.
Отображение для Live Activity
Теперь нужно создать объект который будет отображать наши вью внутри Live Activity и Dynamic Island.
@main
struct TimerACtivityConfiguration: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: TimerAttributes.self) { context in
Text("Hello world")
// Здесь можно добавить view которая будет отображаться в Live Activity на заблокированном экране
} dynamicIsland: { context in
DynamicIsland {
// Здесь можно добавить view которая будет отображаться в Dynamic Island на заблокированном экране
}
}
}
}
На устройстве, которое не поддерживает Dynamic Island, система отображает только баннер на экране блокировки.
Note: Система может обрезать Live Activity если высота она превышает 160 поинтов.
Layout разных типов Dynamic Island
Для настройки разных layout наших Dynamic Island Apple предоставляет нам следующие варианты:
-
DynamicIslandExpandedRegion (Когда Dynamic Island раскрыт):
.leading
.bottom
.trailing
.center
-
Compact (Когда Dynamic Island спрятан):
compactLeading
compactTrailing
minimal
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text("Hello world")
}
DynamicIslandExpandedRegion(.trailing) {
// View
}
DynamicIslandExpandedRegion(.center) {
// View
}
DynamicIslandExpandedRegion(.bottom) {
// View
}
} compactLeading: {
// View
} compactTrailing: {
// View
} minimal: {
Image(systemImage: "checkmark")
}
СompactLeading, CompactTrailing, Minimal:
Когда вы запускаете Live Activity, и это единственное активное Live Activity, оно будет представлено в компактном размере растягиваясь на всю длину. В этот момент слева (compactLeading) и справа (compactTrailing) от TrueDepth камеры появляется место размером 62.33x36.67 куда мы можем добавить свои вьюшки.
Если же в этот момент работает еще какое-то Live Activity помимо вашего приложения, то система сама выберет активити какого приложения нужно показывать. В этот момент Dynamic Island всех приложений будет работать в режиме отображения minimal.
По дефолту compact и minimal layout в Dynamic Island используют черный цвет для фона и белый для текста. Чтобы это изменить можно добавить keylineTint(_:)
модификатор чтобы изменить tint color.
DynamicIsland {
// Реализация
}
.keylineTint(.green)
DynamicIslandExpandedRegion:
Для раскрытого layout нашего Dynamic Island мы можем воспользоваться следующими вариантами:
center
контент который будет находиться под TrueDepth камерой.leading
контент который будет находиться слева от TrueDepth в раскрытом режиме, а так же будет захватывать часть пространства снизу.trailing
контент который будет находиться справа от TrueDepth в раскрытом режиме, а так же будет захватывать часть пространства снизу.bottom
контент который будет находиться под leading, trailing, and center
Для рендеринга содержимого, отображаемого в развернутом виде, система сначала определяет ширину центрального содержимого, принимая во внимание минимальную ширину содержимого слева и справа. Затем система размещает и масштабирует контент слева и справа в зависимости от его вертикального положения. По умолчанию левая и правая зона получает одинаковое количество горизонтального пространства.
Если вы хотите чтобы какой-то области выделялось больше место, а другая ужималась, то можно явно указать приоритет, задав его в инициализаторе init(_:priority:content:)
. По дефолту приоритет выставляется в 0. Система выделяет полную допустимую область той части Dynamic Island, у которой приоритет больше.
Если мы хотим дать нашему контенту больше пространства, то можно использовать .dynamicIsland(verticalPlacement: .belowIfTooWide)
модификатор, чтобы отрисовать контент под TrueDepth камерой.
Жизненный цикл Live Activity
У жизненного Live Activity есть 3 метода которые обновляют его состояние.
request - стартует новое activity
update - обновляет динамические данные
end - останавливает activity
Приложение может запускать несколько Live Activity, и таких приложений может быть несколько — точное число может зависеть от множества факторов. Всегда корректно обрабатывайте любые ошибки при запуске, обновлении или завершении Live Activity. Например, запуск Live Activity может завершиться ошибкой, поскольку на устройстве пользователя может быть достигнут предел количества Live Activity. А так же всегда нужно останавливать активные activity если они больше не нужны чтобы не расходовать лишние ресурсы системы.
Request:
Для того чтобы Live Activity и Dynamic Island отображались нужно вызвать функцию request(attributes:contentState:pushType:)
Эта функция принимает на вход атрибуты и значения для динамического контента который мы указали ранее.
func startActivity() {
let attributes = TimerAttributes(timerName: "Dummy timer")
let state = TimerAttributes.TimerStatus(date: Date())
activity = try? Activity<TimerAttributes>.request(attributes: attributes, contentState: state)
}
В данном примере, для удобства, мы сохранили объект activity в отдельную переменную чтобы потом к ней обращаться. Но так же мы можем получить список всех activity приложения через:
let currentAppActivities = Activity.activities
Начать Live Activity только только когда приложение в foreground mode. Однако мы можем обновить или удалить Live Activity даже когда приложение в background используя - Background Tasks.
Update:
Чтобы обновить наш activity нам нужно обновить только динамические данные размер обновляемых данных не должен превышать 4 КВ:
func updateActivity() {
let state = TimerAttributes.TimerStatus(date: Date())
Task {
await activity?.update(using: state)
}
}
End:
Стоит всегда обновлять актуальные данные перед удалением activity. Это нужно потому что активити пропадает с экрана не сразу, и может какое-то время показывать неактуальные данные, пока его не убьет или не выключит система.
Автоматическое удаление зависит от многих факторов, а так же полиси которое вы указали во время удаления в функции end(using:dismissalPolicy:)
func endActivity() {
let state = TimerAttributes.TimerStatus(date: Date())
Task {
await activity?.end(using: state, dismissalPolicy: .immediate)
}
}
Важно! Всегда удалять activity после того как оно больше не актуально. Activity может быть активной до восьми часов, если ваше приложение или пользователь не удалит его. По истечению восьми часов, система немедленно удаляет его из Dynamic Island. Тем не менее, Live Activity остается на экране блокировки до тех пор, пока пользователь не удалит ее, или еще до четырех часов, прежде чем система удалит ее — в зависимости от того, что наступит раньше. В результате Live Activity остается на экране блокировки до двенадцати часов.
Depp Link и навигация внутри Live Activity
Люди нажимают на Live Activity, чтобы запустить ваше приложение. Чтобы улучшить пользовательский опыт, вы можете использовать widgetURL(_:)
для создание диплинков с вашего экрана блокировки, и Dynamic Island в состояниях compact leading, compact trailing, and minimal.
Dynamic Island в развернутом виде предлагает больше вариантов для кастомизации. Тут мы можем использовать Link
. Например наше приложение по доставке продуктов, может иметь 2 отдельные вьюшки, по нажатии на одну - должна открываться карта, а по нажатию на вторую - список заказанных товаров.
Проверка состояния Live Activity
Live Activities доступен только iPhone. Если ваше приложение портируется на множество платформ, будьте уверены что Live Activities доступен в момент выполнения работы приложения. Так же пользователи могут отключить Live Activity в настройках телефона.
Чтобы проверить что Live Activities доступны на девайсе и пользователь их не отключил:
Используйте
areActivitiesEnabled
чтобы синхронно определить начал ли пользователь Live Activity.Так же можно получить асинхронный калбек через
activityEnablementUpdates
Заключение
В данной статье мы разобрали то как добавить Live Activity в любое приложение, правильно следить за его циклом обновления, а так же какие проблемы это может принести, если это делать неправильно.
nazar228
Спасибо ????