使用实时活动显示实时数据
提供实时活动,在灵动岛和锁定屏幕上显示您的应用程序的最新数据。
概述
实时活动在 iPhone 锁定屏幕和灵动岛中显示您的应用程序的最新数据。这使人们可以一目了然地看到实时信息。
要提供实时活动,请将代码添加到您现有的小部件扩展或创建一个新的小部件扩展(如果您的应用尚未包含)。Live Activity 使用WidgetKit功能和SwiftUI绘制用户界面。ActivityKit 的作用是处理每个 Live Activity 的生命周期:您使用它的 API 来请求、更新和结束 Live Activity。
笔记
现场活动仅在 iPhone 上可用。
要提供实时活动( Live Activity)代码需要添加到小组件中(iOS14 以后的widget)使用WidgetKit功能,使用SwiftUI绘制界面。iOS16新增了ActivityKit,其作用是处理每个 Live Activity 的生命周期:您使用它的 API 来请求、更新和结束 Live Activity。
查看实时活动要求和限制
除非您的应用程序或用户结束它,否则 Live Activity 最多可以处于活动状态八小时。超过此限制,系统自动结束。当 Live Activity 结束时,系统会立即将其从 Dynamic Island 中移除。但是,实时活动会一直保留在锁定屏幕上,直到用户将其删除或系统将其删除前最多四个小时(以先到者为准)。因此,实时活动会在锁定屏幕上最多保留十二小时。
有关结束 Live Activity 的更多信息,请参阅下面的“在您的应用内结束 Live Activity”。
每个 Live Activity 在自己的沙箱中运行,并且 - 与小部件不同 - 它无法访问网络或接收位置更新。要更新活动 Live Activity 的动态数据,请在您的应用程序中使用 ActivityKit 框架或允许您的 Live Activity 接收远程推送通知,如使用远程推送通知更新和结束您的 Live Activity 中所述。
笔记
ActivityKit 更新和远程推送通知更新的更新动态数据大小不能超过 4KB。
实时活动针对锁定屏幕和灵动岛提供不同的视图。锁定屏幕视图出现在所有设备上。支持灵动岛的设备使用以下视图显示实时活动:灵动岛的紧凑前视图、紧凑尾视图、最小视图和扩展视图。
当人在灵动岛中触摸并持有紧凑或最小视图以及实时活动更新时,会出现扩展视图。在不支持灵动岛的解锁设备上,展开的视图显示为实时活动更新的横幅。
为确保系统可以在每个位置显示您的 Live Activity,您必须支持所有视图。
为您的应用添加对实时活动的支持
描述 Live Activity 用户界面的代码是应用的小部件扩展的一部分。如果您已经在应用程序中提供小部件,则可以将 Live Activity 的用户界面代码添加到现有的小部件扩展中,并且可以在小部件和 Live Activity 之间重用代码。然而,尽管 Live Activity 利用了 WidgetKit 的功能,但它们并不是小部件。与您用于更新小部件用户界面的时间线机制相比,您可以使用 ActivityKit 或远程推送通知从您的应用程序更新实时活动。
笔记
您可以创建一个小部件扩展来采用实时活动,而无需提供小部件。但是,请考虑同时提供小部件和实时活动,以允许人们在主屏幕和锁定屏幕上添加可浏览的信息和个人风格。
为您的应用添加对实时活动的支持:
如果您还没有向您的应用程序添加一个小部件扩展,请创建一个小部件扩展。有关创建小部件扩展的更多信息,请参阅WidgetKit和创建小部件扩展。
打开应用程序的
Info.plist
文件,添加Supports Live Activities
条目,并将其布尔值设置为 YES。或者,将 Info.plist 文件作为源代码打开,添加键NSSupportsLiveActivities
,然后将类型设置为布尔值并将其值设置为 YES。如果您的项目没有Info.plist
文件,请将条目添加到您的 iOS 应用程序目标的自定义 iOS 目标属性列表中。添加定义ActivityAttributes结构的代码以描述 Live Activity 的静态和动态数据.
使用您定义的
ActivityAttributes
创建开始实时活动所需的ActivityConfiguration。添加代码以配置、启动、更新和结束您的实时活动。
定义一组静态和动态数据
在为 Live Activity 创建配置对象之前,通过实现 ActivityAttributes 来描述 Live Activity 显示的数据。ActivityAttributes 通知系统有关 Live Activity 中出现的静态数据。您还可以使用 ActivityAttributes 来声明所需的自定义 Activity.ContentState 类型,该类型描述您的 Live Activity 的动态数据。在下面的示例中,PizzaDeliveryAttributes 描述了以下静态数据:订购的比萨饼数量、客户需要支付的金额以及订单号。请注意代码如何定义 Activity.ContentState 来封装动态数据:送披萨的司机的姓名和预计送达时间。此外,该示例定义了类型别名 PizzaDeliveryStatus 以使代码更具描述性和易于阅读。
import Foundation
import ActivityKit
struct PizzaDeliveryAttributes: ActivityAttributes {
public typealias PizzaDeliveryStatus = ContentState
public struct ContentState: Codable, Hashable {
var driverName: String
var deliveryTimer: ClosedRange<Date>
}
var numberOfPizzas: Int
var totalAmount: String
var orderNumber: String
}
为您的实时活动创建配置
添加代码以使用 ActivityAttributes 结构来描述 Live Activity 中显示的数据后,添加代码以在小部件实现中返回 ActivityConfiguration。以下示例使用上一个示例中的 PizzaDeliveryAttributes 结构来配置您的 Live Activity。
import SwiftUI
import WidgetKit
@main
struct PizzaDeliveryActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
// Create the view that appears on the Lock Screen and as a
// banner on the Home Screen of devices that don't support the
// Dynamic Island.
// ...
} dynamicIsland: { context in
// Create the views that appear in the Dynamic Island.
// ...
}
}
}
如果您的应用已经提供小部件,请将 Live Activity 添加到您的 WidgetBundle。如果您没有 WidgetBundle(例如,如果您只提供一个小部件),请按照创建小部件扩展中的描述创建一个小部件包,然后将 Live Activity 添加到其中。以下示例显示了如何使用带有可用性子句的 if 语句仅在设备支持 Live Activity 的情况下将 Live Activity 添加到小部件捆绑包中:
@main
struct PizzaDeliveryWidgets: WidgetBundle {
var body: some Widget {
FavoritePizzaWidget()
if #available(iOS 16.1, *) {
PizzaDeliveryLiveActivity()
}
}
}
创建锁定屏幕视图
要创建 Live Activity 的用户界面,您可以在之前创建的小部件扩展中使用SwiftUI。与小部件类似,您无需为 Live Activity 提供用户界面的大小,而是让系统确定合适的尺寸。
从锁定屏幕上显示的视图开始。以下代码显示PizzaDeliveryAttributes
结构使用标准 SwiftUI 视图描述的信息:
@main
struct PizzaDeliveryWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
// Create the view that appears on the Lock Screen and as a
// banner on the Home Screen of devices that don't support the
// Dynamic Island.
LockScreenLiveActivityView(context: context)
} dynamicIsland: { context in
// Create the views that appear in the Dynamic Island.
// ...
}
}
}
struct LockScreenLiveActivityView: View {
let context: ActivityViewContext<PizzaDeliveryAttributes>
var body: some View {
VStack {
Spacer()
Text("\(context.state.driverName) is on their way with your pizza!")
Spacer()
HStack {
Spacer()
Label {
Text("\(context.attributes.numberOfPizzas) Pizzas")
} icon: {
Image(systemName: "bag")
.foregroundColor(.indigo)
}
.font(.title2)
Spacer()
Label {
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
.multilineTextAlignment(.center)
.frame(width: 50)
.monospacedDigit()
} icon: {
Image(systemName: "timer")
.foregroundColor(.indigo)
}
.font(.title2)
Spacer()
}
Spacer()
}
.activitySystemActionForegroundColor(.indigo)
.activityBackgroundTint(.cyan)
}
}
笔记
如果其高度超过 160 点,系统可能会截断锁定屏幕上的实时活动。
创建紧凑和最小的视图
实时活动出现在支持它的设备的灵动岛中。当您启动一个 Live Activity 并且它是唯一一个活跃的 Live Activity 时,紧凑的前导视图和尾随视图一起出现,以在 Dynamic Island 中形成一个有凝聚力的视图。当多个 Live Activity 处于活动状态时(无论是来自您的应用程序还是来自多个应用程序),系统会选择哪些 Live Activity 可见并使用每个最小视图显示两个:一个最小视图显示为附加到灵动岛,而另一个显示为分离.
笔记
默认情况下,Dynamic Island 中的紧凑和最小视图使用黑色背景颜色和白色文本。使用keylineTint(_:)修改器将可选的色调应用到灵动岛 - 例如,应用青色,如下例所示。
以下示例展示了披萨外卖应用程序如何使用标准 SwiftUI 视图提供所需的紧凑和最小视图:
import SwiftUI
import WidgetKit
@main
struct PizzaDeliveryWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
// Create the view that appears on the Lock Screen and as a
// banner on the Home Screen of devices that don't support the
// Dynamic Island.
// ...
} dynamicIsland: { context in
// Create the views that appear in the Dynamic Island.
DynamicIsland {
// Create the expanded view.
// ...
} compactLeading: {
Label {
Text("\(context.attributes.numberOfPizzas) Pizzas")
} icon: {
Image(systemName: "bag")
.foregroundColor(.indigo)
}
.font(.caption2)
} compactTrailing: {
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
.multilineTextAlignment(.center)
.frame(width: 40)
.font(.caption2)
} minimal: {
VStack(alignment: .center) {
Image(systemName: "timer")
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
.multilineTextAlignment(.center)
.monospacedDigit()
.font(.caption2)
}
}
.keylineTint(.cyan)
}
}
}
创建扩展视图
除了紧凑和最小视图之外,您还必须支持扩展视图。当一个人触摸并持有一个紧凑或最小的视图时,它会出现,并且也会短暂出现以用于实时活动更新。当您更新实时活动时,没有灵动岛的设备也会将扩展视图显示为横幅。使用DynamicIslandExpandedRegionPosition指定您希望 SwiftUI 将内容放置在何处的详细说明。以下示例显示了披萨外卖应用程序如何创建其扩展视图:
@main
struct PizzaDeliveryWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
// Create the view that appears on the Lock Screen and as a
// banner on the Home Screen of devices that don't support the
// Dynamic Island.
LockScreenLiveActivityView(context: context)
} dynamicIsland: { context in
// Create the views that appear in the Dynamic Island.
DynamicIsland {
// Create the expanded view.
DynamicIslandExpandedRegion(.leading) {
Label("\(context.attributes.numberOfPizzas) Pizzas", systemImage: "bag")
.foregroundColor(.indigo)
.font(.title2)
}
DynamicIslandExpandedRegion(.trailing) {
Label {
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
.multilineTextAlignment(.trailing)
.frame(width: 50)
.monospacedDigit()
} icon: {
Image(systemName: "timer")
.foregroundColor(.indigo)
}
.font(.title2)
}
DynamicIslandExpandedRegion(.center) {
Text("\(context.state.driverName) is on their way!")
.lineLimit(1)
.font(.caption)
}
DynamicIslandExpandedRegion(.bottom) {
Button {
// Deep link into your app.
} label: {
Label("Call driver", systemImage: "phone")
}
.foregroundColor(.indigo)
}
} compactLeading: {
// Create the compact leading view.
// ...
} compactTrailing: {
// Create the compact trailing view.
// ...
} minimal: {
// Create the minimal view.
// ...
}
.keylineTint(.yellow)
}
}
}
笔记
如果灵动岛的高度超过 160 点,系统可能会截断灵动岛中的实时活动。
为了呈现展开的 Live Activity 中出现的视图,系统将展开的视图划分为不同的区域。请注意该示例如何返回一个指定多个DynamicIslandExpandedRegion 对象的DynamicIsland。传递以下值DynamicIslandExpandedRegionPosition以在展开视图中的指定位置布置您的内容:
center:将内容放置在原深感摄像头下方。
leading:将内容沿展开的 Live Activity 的前沿放置在原深感摄像头旁边,并在其下方包裹其他内容。
trailing:将内容放置在 TrueDepth 摄像头旁边展开的 Live Activity 的后沿,并在其下方包裹其他内容。
bottom:将内容置于前导、尾随和居中内容之下。
[图片上传失败...(image-96c63b-1665454932342)]
为了呈现展开的 Live Activity 中出现的内容,<u>系统首先确定中心内容的宽度,同时考虑前导和尾随内容的最小宽度</u>。然后系统根据其垂直位置放置前导和尾随内容并确定其大小。默认情况下,前导视图和尾随视图接收相同数量的水平空间。
[图片上传失败...(image-45ee62-1665454932342)]
您可以通过将优先级传递给init(_:priority:content:)初始化程序来告诉系统优先考虑DynamicIslandExpandedRegion
视图之一。系统以动态岛的全宽呈现具有最高优先级的视图。
笔记
如果内容太宽而无法出现在 TrueDepth 相机旁边的前导位置,请使用belowIfTooWide修饰符来渲染 TrueDepth 相机下方的前导内容。
使用自定义颜色
默认情况下,系统使用默认的文本原色和最适合用户锁定屏幕的 Live Activity 的背景颜色。要设置自定义色调颜色,请使用视图修饰符activityBackgroundTint(_:)。此外,使用activitySystemActionForegroundColor(_:)视图修饰符自定义允许人们在锁定屏幕上结束实时活动的辅助按钮的文本颜色。
要设置自定义背景色调颜色的半透明,请使用opacity(_:)视图修改器或指定不透明背景颜色。
笔记
在包括 Always-On Retina 显示屏的设备上,系统会调暗屏幕以延长电池寿命,并在锁定屏幕上呈现实时活动,就像在暗模式下一样。使用 SwiftUI 的isLuminanceReduced环境值来检测 Always On 并使用在 Always On 中看起来很棒的图像。
在您的应用中创建深层链接
人们点击实时活动来启动您的应用程序。为了改善用户体验,您可以使用widgetURL(_:)从锁定屏幕、紧凑的前导、紧凑的尾随和最少的视图创建到您的应用程序的深层链接。当紧凑的前导视图和尾随视图可见时,请确保它们都链接到应用程序中的同一屏幕。
扩展视图提供了额外的选项来创建应用程序的深层链接,以便使用 SwiftUI 的Link.例如,披萨外卖应用程序可能包含两个 SwiftUI 视图。一个视图可以在应用程序中打开当前送货的地图,第二个视图可以打开一个屏幕,让人们可以打电话给送披萨的人。
确保现场活动可用
现场活动仅在 iPhone 上可用。如果您的应用可在多个平台上使用并提供小部件扩展,请确保实时活动在运行时可用。此外,用户可以在“设置”应用中选择停用应用的实时活动。
要查看 Live Activity 是否可用以及用户是否允许您的应用使用 Live Activity:
- 使用areActivitiesEnabled同步确定是否在您的应用中显示用户界面以启动 Live Activity。
- 通过使用activityEnablementUpdates观察流中的任何用户授权更改来接收异步用户授权更新并相应地响应它们。
笔记
一个应用可以启动多个 Live Activity,而一个设备可以从多个应用运行 Live Activity。除了确保 Live Activity 可用之外,在开始、更新或结束 Live Activity 时始终优雅地处理任何错误。例如,启动 Live Activity 可能会失败,因为用户的设备可能已达到其活动 Live Activity 的限制。
开始现场活动
当应用程序在前台时,您可以在应用程序代码中使用request(attributes:contentState:pushType:)函数启动 Live Activity。它将您创建的属性和内容状态作为参数来提供显示在 Live Activity 中的初始值,并告诉系统哪些数据是动态的。如果您实施远程推送通知来更新 Live Activity,还需要提供 pushType
参数。
以下代码示例从前面的示例中为披萨外卖应用启动了一个新的 Live Activity:
var future = Calendar.current.date(byAdding: .minute, value: (Int(minutes) ?? 0), to: Date())!
future = Calendar.current.date(byAdding: .second, value: (Int(seconds) ?? 0), to: future)!
let date = Date.now...future
let initialContentState = PizzaDeliveryAttributes.ContentState(driverName: "Bill James", deliveryTimer:date)
let activityAttributes = PizzaDeliveryAttributes(numberOfPizzas: 3, totalAmount: "$42.00", orderNumber: "12345")
do {
deliveryActivity = try Activity.request(attributes: activityAttributes, contentState: initialContentState)
print("Requested a pizza delivery Live Activity \(String(describing: deliveryActivity?.id)).")
} catch (let error) {
print("Error requesting pizza delivery Live Activity \(error.localizedDescription).")
}
请注意上面的代码片段如何不传递pushType
参数并在不使用远程推送通知更新其内容的情况下启动 Live Activity。它还将返回的 Live Activity 对象存储在可用于更新和结束 Live Activity 的deliveryActivity
属性中。有关使用远程推送通知更新您的实时活动的更多信息,请参阅使用远程推送通知更新和结束您的实时活动。
笔记
您只能在应用程序处于前台时从应用程序启动实时活动。但是,您可以在应用程序在后台运行时更新或结束 Live Activity - 例如,通过使用Background Tasks。
更新实时活动
当您从应用程序启动 Live Activity 时,使用您在启动 Live Activity 时收到的Activity对象的update(using:)函数更新显示在 Live Activity 中的数据。要检索应用程序的活动实时活动,请使用activities.
例如,披萨配送应用程序可以更新显示配送状态的 Live Activity,其中包含新的配送时间和新的司机。它还可以使用update(using:alertConfiguration:)函数在 iPhone 和 Apple Watch 上显示警报,告知人们新的 Live Activity 内容,如下例所示:
var future = Calendar.current.date(byAdding: .minute, value: (Int(minutes) ?? 0), to: Date())!
future = Calendar.current.date(byAdding: .second, value: (Int(seconds) ?? 0), to: future)!
let date = Date.now...future
let updatedDeliveryStatus = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "Anne Johnson", deliveryTimer: date)
let alertConfiguration = AlertConfiguration(title: "Delivery Update", body: "Your pizza order will arrive in 25 minutes.", sound: .default)
await deliveryActivity?.update(using: updatedDeliveryStatus, alertConfiguration: alertConfiguration)
笔记
更新数据的大小不能超过 4KB。
在 Apple Watch 上,系统将title
和body
属性用于alert
。在 iPhone 上,系统不会显示常规alert
,而是显示灵动岛中展开的实时活动。在不支持灵动岛的设备上,系统会在主屏幕上显示一个横幅,该横幅使用您的实时活动的扩展视图。
动画内容更新
当您定义 Live Activity 的用户界面时,系统会忽略任何动画修改器,例如withAnimation(::),和animation(_:value:),而是使用系统的动画时间。但是,当 Live Activity 的动态内容发生变化时,系统会执行一些动画。文本视图通过模糊的内容过渡动画内容变化,并且系统为图像和 SF 符号动画内容过渡。如果您根据内容或状态更改从用户界面添加或删除视图,视图会淡入淡出。使用以下视图转换来配置这些内置转换:opacity、move(edge:)、slide、push(from:)或它们的组合。此外,使用numericText(countsDown:)请求计时器文本的动画。
笔记
在包含 Always On Retina 显示屏的设备上,系统不会执行动画以保持 Always On 的电池寿命。确保在动画内容更改之前使用 SwiftUI 的isLuminanceReduced环境值来检测 Always On。
从您的应用程序中结束实时活动
始终在关联的任务或实时事件结束后结束实时活动。已结束的实时活动将保留在锁定屏幕上,直到用户将其删除或系统自动将其删除。自动删除取决于您为函数end(using:dismissalPolicy:)提供的解雇策略。此外,始终包含更新的Activity.ContentState以确保实时活动在结束后显示最新和最终的内容更新。这很重要,因为实时活动可以在锁定屏幕上保持可见一段时间。
以下示例显示了披萨外卖应用程序如何结束一个实时活动,该活动显示比萨送达时订单的交付状态:
let finalDeliveryStatus = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "Anne Johnson", deliveryTimer: Date.now...Date())
Task {
await deliveryActivity?.end(using:finalDeliveryStatus, dismissalPolicy: .default)
}
上面的示例使用default解雇政策。因此,实时活动结束后会在锁定屏幕上显示一段时间,以允许用户浏览他们的手机以查看最新信息。用户可以随时选择移除 Live Activity,或者系统在活动结束四小时后自动移除。
要立即删除从锁定屏幕结束的实时活动,请使用immediate。或者,用于after(_:)`指定四小时窗口内的日期。虽然您可以提供任何日期,但系统会在给定日期之后或在实时活动结束后四个小时后删除已结束的实时活动 - 以先到者为准。
用户可以随时从锁定屏幕中删除您的实时活动。这会结束您的 Live Activity,但不会结束或取消用户启动 Live Activity 的操作。例如,用户可以从锁定屏幕中删除他们的披萨外卖的实时活动,但这不会取消披萨订单。当用户或系统删除实时活动时ActivityState更改为ActivityState.dismissed。
使用远程推送通知更新或结束您的实时活动
除了使用 ActivityKit 从您的应用程序更新和结束实时活动之外,您还可以使用从服务器发送到 Apple 推送通知服务 (APN) 的远程推送通知更新或结束实时活动。要了解有关使用远程推送通知更新您的实时活动的更多信息,请参阅使用远程推送通知更新和结束您的实时活动。
跟踪更新
当您启动 Live Activity 时,ActivityKit 会返回一个Activity对象。除了唯一标识每个活动的id之外,Activity还提供观察内容状态、活动状态和推送令牌更新的序列。使用相应的序列在您的应用中接收更新,使您的应用和 Live Activity 保持同步,并响应更改的数据:
要观察正在进行的 Live Activity 的状态——例如,确定它是处于活动状态还是已经结束——使用activityStateUpdates
要观察 Live Activity 动态内容的变化,请使用contentState
要观察 Live Activity 的推送令牌的变化,请使用pushTokenUpdates
获取活动列表
您的应用可以启动多个 Live Activity。例如,体育应用程序可能允许用户为他们感兴趣的每个现场体育比赛启动Live Activity。如果您的应用程序启动多个Live Activity,请使用activityUpdates函数获取有关您的应用程序正在进行的Live Activity的通知。跟踪正在进行的 Live Activity 以确保您的应用程序的数据与 ActivityKit 跟踪的活动 Live Activity 同步。
以下代码段显示了披萨外卖应用程序如何检索正在进行的活动列表:
// Fetch all ongoing pizza delivery Live Activities.
for await activity in Activity<PizzaDeliveryAttributes>.activityUpdates {
print("Pizza delivery details: \(activity.attributes)")
}
获取所有活动的另一个用例是维护正在进行的实时活动,并确保您不会让任何活动持续运行超过需要的时间。例如,系统可能会停止您的应用程序,或者您的应用程序可能会在 Live Activity 处于活动状态时崩溃。当应用下次启动时,检查是否有任何活动仍处于活动状态,更新应用存储的 Live Activity 数据,并结束任何不再相关的 Live Activity。
也可以看看
现场活动实施
使用远程推送通知更新和结束您的实时活动
当您开始实时活动时接收推送令牌,并使用它通过远程推送通知更新或结束您的实时活动。
class Activity 用于启动、更新和结束实时活动的对象。