一点废话
iOS>=17.2开始允许通过APNs启动Live Activity。
先说限制
- 第一次打开app 未启动实时活动,无法获取到pushToStartToken
- 一个ActivityAttributes类所有的 pushToStartToken 都相同
基本流程
graph TD
启动App --> 启动一个临时的ActivityAttributes --> 获取pushToStartToken --> 上传pushToStartToken --> 关闭临时ActivityAttributes --> 在需要的时候APNs打开实时活动
实现
工程设置
1. 开启 Push Notifications
- 在 Xcode →
Signing & Capabilities→ 添加 Push Notifications。 - 没有这个 Capability,系统不会给 App 分配 Push-to-Start Token。
2. 开启 Background Modes → Remote notifications
- 进入
Signing & Capabilities,勾选 Background Modes → 选中 Remote notifications。 - 否则 App 在前后台都不会触发 Token 更新。
3. App ID / Provisioning Profile
- 你的 App ID 必须启用了 Push Notifications。
- Profile 要重新生成并下载,确保包含了
aps-environmententitlement。 可以在xxx.entitlements文件里确认有这一项:
<key>aps-environment</key>
<string>development</string>
4. Live Activity 配置
-
在 Xcode 的 target 里,确保
Widget Extension的 Info.plist 里:<key>NSExtension</key> <dict> <key>NSExtensionPointIdentifier</key> <string>com.apple.widgetkit-extension</string> </dict> -
并且主 App 和 Widget Extension 的 Bundle ID 必须属于同一个 App Group。
代码部分
启动临时一个实时活动获取pushToStartToken,启动方法Activity.request(..., pushType: .token) pushType 必须用 .token ,否则无法获取到 pushToStartToken
启动后获取 pushToStartToken 上传服务端
小提示
用xcode debug 时可以不创建一个临时实时活动也能获取 pushToStartToken,而且每次重新编译 pushToStartToken 都会变
上传完成后关闭临时实时活动
func tempActivity() {
do {
let activity = try Activity.request(
attributes: MyWidgetAttributes(...),
content: .init(state: MyWidgetAttributes.ContentState(...), staleDate: nil),
pushType: .token // 必须用.token
)
Task {
if let tokenData = await Activity<LiveActivitiesWidgetAttributes>
.pushToStartTokenUpdates
.first(where: { _ in true }) {
// 获取pushToStartToken 上传服务端
let mytoken = tokenData.map { String(format: "%02x", $0) }.joined()
}
self.dismissal()
}
} catch (let error) {
print("请求开启实时出错: \(error.localizedDescription)")
}
}
func dismissal() {
for activity = Activity<LiveActivitiesWidgetAttributes>.activities {
Task {
await activity.end(
ActivityContent(state: MyWidgetAttributes.ContentState(...), staleDate: nil),
dismissalPolicy: dismissalPolicy)
}
}
}
完成后可以再开启一个Task监听 pushToStartToken,每次开启一次实时活动都可以通过监听拿到 pushToStartToken
func getPushToStartToken() {
//pushToStartToken为空。App创建LA时,会触发下面的回调。可以拿到pushToStartToken
Task {
for await tokenData in Activity<MyWidgetAttributes>.pushToStartTokenUpdates {
//监听token更新 注意线程
let mytoken = tokenData.map { String(format: "%02x", $0) }.joined()
//TODO: 开发者上传变更后的startToken
}
// for 循环以后的代码不会执行
// for await 会 一直挂起等待新事件,只有当序列 结束(complete/throw) 时,才会跳出循环。
}
}
服务端发送 APNs 启动实时活动
请求头
POST https://api.sandbox.push.apple.com/3/device/{PushToStartToken}
authorization: bearer <your-auth-token>
apns-topic: <你的主App Bundle ID>.push-type.liveactivity
apns-push-type: liveactivity
请求体
{
"aps": {
"timestamp": 1692243600,
"event": "start",
"content-state": {
"status": "配送中",
"progress": 10
},
"alert": {
"title": "订单已出发",
"body": "骑手正在赶来 🚴"
}
}
}
event: "start"表示 启动一个新的 Live Activitycontent-state是你定义的MyWidgetAttributes.ContentState的 JSON 映射,每个字段都要实现否则系统解码时会出错alert可选,如果提供会在锁屏/通知中心显示一条启动提示
调试工具
做好证书后可以自己发APNS 测试:apnspush.com/
推送证书用主app Bundle ID 做的 APNs 证书
系统提供的Apple Notifications Dashboard 因为不能修改apns-topic,无法启动实时活动