简介
之前在 《iOS16新特性:灵动岛适配开发与到家业务场景结合的探索实践》 里介绍了iOS16新的特性:实时更新(Live Activity)中灵动岛的适配流程,但其实除了灵动岛的展示样式,Live Activity还有一种非常实用的应用场景,那就是锁屏界面实时状态更新:
上图是部分已经做出适配的APP,锁屏实时活动的展示。可以看到,相比于灵动岛的样式,锁屏更新的展示区域更大,能够显示更多信息,并且是在锁屏界面上进行展示,结合苹果在iPhone14之后推出的“全天候显示”功能,能够让用户在不解锁手机,甚至不拿起手机的情况下就能够获取到APP内最新的消息更新,在某些应用场景下非常实用。
这篇文章主要就介绍Live Activity中锁屏实时活动样式的适配流程,再结合实际开发过程中的遇到的问题进行实际详解:
限制条件
在进行开发之前,需要先了解一下锁屏实时活动的一些限制条件:
1.实时活动显示在通知区域且有更自由的视图定制和刷新方法,但是跟Widget小组件一样,它也限制了视图上的动画开发,所有的动画效果仅能由系统处理。
2.锁屏通知区域内的实时活动在8小时之内可以刷新数据展示,超过8小时不再支持刷新,,超过12小时强制消失
3.实时活动视图本体不支持发起网络请求,所有的动态数据都要经由通知下发,或者后台活动数据刷新,且每次更新的数据不能超过4KB。
4.实时活动可以通过推送下发更新数据,但是推送的类型不同于传统“基于证书”的推送,而是“基于token”的推送类型。
实际开发
1.建立锁屏实时活动扩展项目
这部分建立的过程与灵动岛的适配流程完全一致,请参见 iOS16新特性:灵动岛适配开发与到家业务场景结合的探索实践 中相关的流程描述,如果之前建立过灵动岛项目,则可以直接开始开发:
2.UI开发
Live Activity的全部样式开发均完全采用SwiftUI,锁屏实时活动也不例外,以下是我开发的UI部分代码,大家可以一参考一下:
struct LockScreenLiveActivityView: View {
let context: ActivityViewContext<DJDynamicIslandAttributes>
var body: some View {
VStack {
Spacer(minLength: 10)
LockScreenLiveActivityStoreHeaderView(imageURL: context.state.logo, title: context.state.title, subTitle: context.state.subTitle)
Spacer(minLength: 0)
LockScreenLiveActivityProgressView(progress: context.state.progress)
Spacer(minLength: 10)
}
}
}
struct LockScreenLiveActivityStoreHeaderView: View {
let imageURL: String
let title: String
let subTitle: String
var body: some View {
HStack(spacing: 10) {
NetworkImage(imageUrl: imageURL)
.frame(width: 50, height: 50)
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(title)
.font(.system(size: 16, weight: .bold))
.foregroundColor(Color(hex: 0x333333, alpha: 1))
}
Text(subTitle)
.font(.system(size: 13))
.foregroundColor(Color(hex: 0x666666, alpha: 1))
.padding(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0))
}
Spacer() // 填充剩余空间
}
.padding(8)
}
}
struct LockScreenLiveActivityProgressView: View {
var progress: CGFloat
let borderOffset = 20.0
var body: some View {
VStack {
ZStack(alignment: .bottom) {
HStack(alignment: .bottom) {
Spacer()
NetworkImage(imageUrl: "", placeholdImage: "store")
.frame(width: 50, height: 50)
Spacer()
}
HStack(alignment: .bottom) {
NetworkImage(imageUrl: "", placeholdImage: "knight")
.frame(width: 40, height: 40)
.offset(x: progress * UIScreen.main.bounds.width - 25)
Spacer()
}
HStack(alignment: .bottom) {
Spacer()
NetworkImage(imageUrl: "", placeholdImage: "pin")
.frame(width: 18, height: 25)
.offset(x: -borderOffset)
}
}
.frame(height: 50)
Spacer(minLength: 0)
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.gray)
.frame(height: 10)
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.yellow)
.frame(width: (UIScreen.main.bounds.width - borderOffset * 3) * progress, height: 10)
}
.frame(height: 15)
.padding(.horizontal, borderOffset)
}
}
}
运行起来以后大概长这个样子:
坑1:
由于实时活动不允许加载网络请求,所以网络图片的URL也无法加载,可以通过:
1.直接通推送通知过下发图片的Data,再转成img,但是要注意数据大小,不要超过4Kb
2.本地图片
来解决
3.Live Activity的生命周期
Live Activity的生命周期由ActivityKit管理,其中,数据部分的模型类为ActivityAttributes,自定义数据模型需要继承自ActivityAttributes,静态数据变量直接生命在结构体内,动态数据变量需要声明在ActivityAttribut