1.简单介绍
ActivityKit是2022年新发布的,在开始iOS16上提供的新功能:提供实时活动的预览能力,在灵动岛和锁定屏幕、消息中心上显示应用程序的最新数据,能够大大提升用户获取相关实时信息的能力。其中也存在以下的限制和不足之处:
整个灵动岛活动最多只能维持八个小时的时间
可以通过 ActivityKit 或者 ActivityKit push notifications 来配置、启动、更新与终止 Live Activity但二者在更新时的动态数据大小均不能超过 4 KB
每个 Activity 对应一个推送token,Activity 中止后,令牌失效
可以通过Link实现点击不同区域,跳转到不同页面。如果只使用widgetURL的方式跳转,那么仅可以跳转到一个scheme页面。
2.应用场景
应用场景主要是需要给用户一个入口快速查看活动的进度,使用户不用点进app就能看到这个活动的进度,官方文档是采用一个送披萨的例子,用户外卖点了披萨配送,这时候app可以申请一个 LiveActivity ,通过常驻的消息中心和灵动岛来显示实时进度,并且在披萨到达以后可以推送通知。目前主要在体育赛事记分展示上、计时应用、当前播放的音乐内容、显示相关导航信息、上传/下载速度和进度等这些应用中可以相关功能。
3.展示类型和界面区域划分
1.紧凑型
在灵动岛中,当只有一个当前处于活动状态的实时活动时,系统使用紧凑的展示。紧凑的展示由 2 个单独的展示组成:其中 1 个显示在原深感摄像头前侧,另 1 个显示在后侧。虽然前侧展示和后侧展示是单独的视图,但它们在灵动岛中形成了一个结合的视图,代表了 app 中的一条单独的信息。用户可通过轻按紧凑的实时活动来打开 app,并获取关于事件或任务的更多详细信息。
紧凑型内容区域
对应的代码参数界面
//左侧Lead样式
compactLeading: {
// 紧凑型样式左边 UI
Text("灵动岛未展开的左边")
} compactTrailing: {//右侧Trail样式
// 紧凑型样式右边 UI
Text("灵动岛未展开的右边 \(context.state.emoji)")
}
2.最小型
当多个实时活动处于活动状态时,系统通过使用最小的展示来在灵动岛中显示其中的 2 个活动。其中 1 个实时活动显示为附属到动态岛,另 1 个显示为与灵动岛分离。根据其陆内容大小,分离状态的最小实时活动显示为圆形或椭圆形。与紧凑的灵动岛一样,用户通过轻按最小的实时活动来打开 app,用户长按也是可以讲其由最小型切换到扩展型 ,并获取关于事件或任务的更多详细信息。最多支持两个灵动岛同时进行展示,其中采取先来后到的原则,最后开始的灵动岛是attached的状态,之前开始的就是detached的状态,点击长按的时候展示扩展型
对应的代码参数界面
minimal: {
// 最小型样式 UI
Text("灵动岛Mini \(context.state.emoji)")
}
3.扩展型
当用户在紧凑或最小的展示中长按 1 个实时活动时,系统会以扩展展示的方式显示内容。点击紧凑型之后就会显示扩展型的,扩展型的每个区域的如下,
1.center将内容放置在TrueDepth相机下方。
2.leading将内容放置在TrueDepth相机旁边的扩展Live Activity的前缘,并在其下方包装其他内容。
3.Trailing将内容放置在TrueDepth相机旁边的扩展Live Activity的后缘,并在其下方包装额外的内容。
4.Bottom将内容置于前导、尾随和居中内容之下。
对应的代码参数界面
DynamicIsland {
// 展开样式的 UI界面
DynamicIslandExpandedRegion(.leading) {
Text("灵动岛展开的左边")
}
DynamicIslandExpandedRegion(.trailing) {
Text("灵动岛展开的右边")
}
DynamicIslandExpandedRegion(.bottom) {
Text("灵动岛展开的地步 \(context.state.emoji)")
// more content
}
}
4.锁屏(Lock Screen)
在锁屏和不支持灵动岛的设备上,系统通过使用锁屏展示来在屏幕顶部显示横幅。锁屏展示会在用户查看主屏幕或使用另 1 个 app 时短暂出现,但仅限于 app 确定这个更新足够重要到可以打断用户。锁屏展示界面的用户,需要在第一次使用的时候给予始终允许的权限,否则会导致锁屏界面刷新不及时,出现不同步的问题
对应的代码参数界面
ActivityConfiguration(for: KPWidgetAttributes.self) { context in
// Lock screen/banner UI goes here HStack {
VStack (alignment: .leading){
HStack {
Image("ic_get_up")
Text("Time to get up now")
.fontWeight(.medium)
.font(.system(size: 16))
.foregroundColor(Color(hex: "666666"))
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
Text(timerInterval: context.state.monitorTime,countsDown: false)
.fontWeight(.semibold)
.font(.system(size: 36))
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
Spacer()
ZStack (alignment: .center){
Text("sdsadsadsasa2")
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
.background (Color(hex: "333333",alpha: 0.16))
.cornerRadius(22)
.border(Color(hex: "333333",alpha: 0.16),width: 0)
}.padding(EdgeInsets(top: 10, leading: 0, bottom: -30, trailing: 0))
}.padding(EdgeInsets(top: 30, leading: 20, bottom: 30, trailing: 20))
.activityBackgroundTint(Color(hex: "F7DFC6"))
.activitySystemActionForegroundColor(nil)
}
4.展示界面的参数规格
下面是一个设备在使用liveActivity的情况下,相关状态下的页面大小的参照参数
5.使用介绍
配置权限
1.在主工程项目中,Info-Plist文件加入Bool类型的权限key值为:Supports Live Activities value 为:YES
2.在手机系统通用设置界面中->找到该App->打开支持实时活动开关
创建
由于ActivityKit是基于widgetKit的,所以接下来是给我们的项目创建一个小组件extension,选择创建文件的路径File->Target->WidgetExtension,在创建Extension的时候,在
主入口
@mainstruct LiveActTestBundle: WidgetBundle {
var body: some Widget {
LiveActTest()
LiveActTestLiveActivity()
}}
搭建展示界面视图
struct LiveActTestLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: KPWidgetAttributes.self) { context in
// 锁屏状态下的界面展示信息
HStack {
VStack (alignment: .leading){
HStack {
Image("ic_get_up")
Text("Time to get up now")
.fontWeight(.medium)
.font(.system(size: 16))
.foregroundColor(Color(hex: "666666"))
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
Text(timerInterval: context.state.monitorTime,countsDown: false)
.fontWeight(.semibold)
.font(.system(size: 36))
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
Spacer()
ZStack (alignment: .center){
Text("sdsadsadsasa2")
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
.background (Color(hex: "333333",alpha: 0.16))
.cornerRadius(22)
.border(Color(hex: "333333",alpha: 0.16),width: 0)
}.padding(EdgeInsets(top: 10, leading: 0, bottom: -30, trailing: 0))
}.padding(EdgeInsets(top: 30, leading: 20, bottom: 30, trailing: 20))
.activityBackgroundTint(Color(hex: "F7DFC6"))
.activitySystemActionForegroundColor(nil)
} dynamicIsland: { context in
DynamicIsland {
///点击之后的展开状态----展开之后分为不同的区域,大概总共三个区域
// Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom
DynamicIslandExpandedRegion(.leading) {
VStack (alignment: .leading){
HStack {
Image("ic_in_bed")
Text("DynamicIsland1")
.fontWeight(.medium)
.font(.system(size: 16))
.foregroundColor(Color(hex: "FFFFFF",alpha:0.5))
}
Text(timerInterval: context.state.monitorTime,countsDown: false)
.fontWeight(.semibold)
.font(.system(size: 18))
.foregroundColor(Color(hex: "FFFFFF"))
}.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))
}
DynamicIslandExpandedRegion(.trailing) {
ZStack (alignment: .center){
Text(NSLocalizedString("Stop", comment: ""))
.fontWeight(.bold)
.font(.system(size: 14))
}.padding(EdgeInsets(top: 30, leading: 0, bottom: 0, trailing:5))
}
DynamicIslandExpandedRegion(.bottom) {
// more content
}
} compactLeading: {
///单个收起状态左侧
Image("ic_in_bed")
.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0))
} compactTrailing: {
///单个收起状态右侧
Text(timerInterval: context.state.monitorTime,countsDown: false)
.fontWeight(.semibold)
.font(.system(size: 18))
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 10))
} minimal: {
//最小话状态
Image("ic_in_bed")
}
.widgetURL(URL(string: "http://www.apple.com"))
.keylineTint(Color.gray)
}
}}
如果项目本身是swift项目,那么直接在主工程中直接去调用对应的类即可,但是如果主工程是oc项目,那么就需要oc调用swift中的类
oc调用swift
找到工程文件下的TARGETS -->Build settings中搜索 Product Module Name设置为工程名,这时工程会自动创建一个"项目名"-Swift.h的文件,最后在OC文件需要使用的地方里面#import "项目名称-Swift.h", 注意这个文件没有提示,要手动打出来,然后就能在OC类里面使用swift的类了,创建ActivityAttributes类和ActivityManager管理类,将其中的管理方法和展示方法放在主工程中去,在需要调用的地方引用"项目名"-Swift.h的文件,实现oc对swift中管理类的调用
struct KPWidgetAttributes:ActivityAttributes{//当前需要用到的参数
public typealias KPWidgetState = ContentState
public struct ContentState: Codable, Hashable {
//参数,当前的监测时间
var monitorTime: ClosedRange<Date>
var sleepProgress: Double
var activityType: Bool
}
var sleepGoal: Double}
///启动活动
@objc public func startActivity(sleepGoal:Int,currentDate:Date,type:Bool){
self.startTimer()
let attributes = KPWidgetAttributes(sleepGoal: Double(sleepGoal))
let initialConetntState = KPWidgetAttributes.KPWidgetState(monitorTime: StartDate...Date().addingTimeInterval(12 * 60 * 60 ),sleepProgress: kpActivityManager.progress, activityType: type)
do {
let activity = try Activity<KPWidgetAttributes>.request(attributes: attributes, contentState: initialConetntState, pushType: nil)
print("Requested a pizza delivery Live Activity \(activity.id)")
} catch (let error) {
print("Error requesting pizza delivery Live Activity \(error.localizedDescription)")
}
}
///停止活动
@objc public func stopActivity (){
Task {
for activity in Activity<KPWidgetAttributes>.activities{
await activity.end(dismissalPolicy: .immediate)
}
}
self.cancelTimer()
}
6.最终效果展示预览
参考文章
1.官方文档
2.iOS16 基于ObjectiveC的实时活动以及灵动岛
3.iOS 小组件开发第八篇:灵动岛开发
4.SwiftUI 官方教程