官方文档
简单介绍
ActivityKit是iOS16上提供的新功能:提供实时活动的预览能力,在灵动岛和锁定屏幕、消息中心上显示应用程序的最新数据。
应用场景
应用场景主要是我们的app创建了一个持续性的活动,需要给用户一个入口快速查看活动的进度,使用户不用点进app就能看到这个活动的进度,官方文档是采用一个送披萨的例子,用户外卖点了披萨配送,这时候app可以申请一个 LiveActivity ,通过常驻的消息中心和灵动岛来显示实时进度,并且在披萨到达以后可以推送通知。
使用
ActivityKit是基于WidgetKit 和 SwiftUI 的框架,我们要实现其基本功能和小组件类似,可以理解为展示在通知中心的小组件,并且可以在需要的时候更新。
LiveActivity
LiveActivity是展示在通知中心的实时活动,首先我们需要声明一个实现ActivityAttributes协议对象,这个对象可以理解为是我们展示LiveActivity的数据源,当我们需要更新展示的内容的时候,需要通过它来更新数据
ActivityAttributes的声明
public protocol ActivityAttributes : Decodable, Encodable {
/// The associated type that describes the dynamic content of a Live Activity.
///
/// The dynamic data of a Live Activity that's encoded by `ContentState` can't exceed 4KB.
associatedtype ContentState : Decodable, Encodable, Hashable
}
其中要求我们使用的时候必须声明一个ContentState作为这个数据源的动态类型,数据变更依据这个ContentState来改变,在官方的示例中时这样做的
struct PizzaDeliveryAttributes: ActivityAttributes {
public typealias PizzaDeliveryStatus = ContentState
public struct ContentState: Codable, Hashable {
var driverName: String
var estimatedDeliveryTime: Date
}
var numberOfPizzas: Int
var totalAmount: String
}
这个PizzaDeliveryAttributes,披萨的数量、总价基本是不变的,所以不需要作为动态的类型显示,司机名字和预计交付时间可能会实时变化,所以设定为ContentState,作为目前配送的状态。
按照这个Demo仿写一个属性,假设我在包房进入后台之后需要开启一个LiveActivity,数据源设定以下属性,需要我更新的动态属性写进ContentState
struct KTVLRPlayAttributes: ActivityAttributes {
public typealias status = ContentState
public struct ContentState: Codable, Hashable {
var musicName: String
var singerName: String
var singerIconUrl: URL
}
public var roomName: String
public var roomId: String
}
由于ActivityKit是基于widgetKit的,所以接下来是给我们的项目创建一个小组件extension
然后需要在项目的info.plist文件中添加一个属性 Supports Live Activities 并且设置为YES
这个时候就可以编写LiveActivity的UI了
struct KTVLRPlayLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration<KTVLRPlayAttributes> { context in // LiveActivity code...
} dynamicIsland: { context in // dynamicIsland code ...
}
}
}
LiveActivity的UI和小组件一样,使用SwiftUI编写即可(没咋写过SwiftUI,api不熟悉,简单示例一下)
struct LiveActivityView: View {
var context: ActivityViewContext<KTVLRPlayAttributes>
var body: some View {
HStack {
Spacer().frame(width: 20)
VStack(alignment: .leading) {
HStack {
Spacer().frame(width: 10)
Label {
Text("唱吧")
} icon: {
Image("Activity_share_icon_changba").resizable().frame(width: 25, height: 25)
}
.font(.body)
Spacer()
Label {
Text("点击返回房间")
} icon: {
}
}
HStack {
Label {
Text("当前在(context.attributes.roomName)")
} icon: {
}
Spacer()
Label {
Text("(context.state.singerName) 正在发言")
} icon: {
}
}
}
Spacer().frame(width: 20)
}
}
}
Dynamic Island
灵动岛状态分为3种:
- 单个收起状态
- 最小化状态(分两种情况,不止一个灵动岛的时候会触发)
这里灵动岛的顺序是按照这个岛产生的先后顺序来的,先产生的会被放在右边
\
- 展开状态
最大高度为160
\
将我们的数据填进去,通过swiftui编写视图:
func createDynamicIslandView(context: ActivityViewContext<KTVLRPlayAttributes>) -> DynamicIsland {
return DynamicIsland {
// 展开的区域
DynamicIslandExpandedRegion(.leading) {
Spacer().frame(height: 10)
Image("changba_icon")
}
DynamicIslandExpandedRegion(.trailing) {
Spacer().frame(height: 30)
Text("返回唱吧")
}
DynamicIslandExpandedRegion(.center) {
Text("您正在(context.attributes.roomName)")
}
DynamicIslandExpandedRegion(.bottom) {
Text("(context.state.singerName) 正在演唱")
}
} compactLeading: {
// 收起时候左侧
Image("changba_icon")
} compactTrailing: {
// 收起时候右侧
Text("唱吧")
} minimal: {
// 最小化UI
Image("changba_icon")
}
}
效果如下:
\
结束一个LiveActivity
在合适的地方调用:
Task {
if let activity = self.activity {
await activity.end(dismissalPolicy: .immediate)
self.activity = nil }
}
这是我的VC中持有了这个activity,如果没有持有或者要终止某一类所有的activity,可以直接通过下面的方法查询:
for activity in Activity<XXXActivityAttributes>.activities {
// xxxxxxxxxx
}
\
更新LiveActivity
本地更新:
还是拿到需要更新的activity,调用update方法即可
Task {
if let activity = self.activity {
await activity.update(using: Activity<KTVLRPlayAttributes>.ContentState(musicName: "音乐名字", singerName: "歌手名字"))
}
}
远程更新:
Updating and ending your Live Activity with remote push notifications | Apple Developer Documentatio
Tips
不论是通知中心还是灵动岛的UI 都无法响应点击事件,整个区域点击会进入app,经过调研,可以通过Link的形式来对不同区域的点击有不同的响应,通过预定的Url来达到点击进入App后进入不同的页面、调用不同的功能等效果,无论是小组件还是LiveActivity,更多的应该是展示状态,而不能在上面实现更多的功能