2023 App Services
WWDC23 · 18 min · App Services
Update Live Activities with push notifications
Discover how you can remotely update Live Activities in your app when you push content through Apple Push Notification service (APNs). We’ll show you how to configure your first Live Activity push locally so you can quickly iterate on your implementation. Learn best practices for determining your push priority and configuring alerting updates, and explore how to further improve your Live Activities with relevance score and stale date. To get the most out of this session, you should be familiar with ActivityKit and Live Activities. Check out “Meet ActivityKit” for an introduction to Live Activities.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 11 snippets
Enabling push updates
func startActivity(hero: EmojiRanger) throws {
let adventure = AdventureAttributes(hero: hero)
let initialState = AdventureAttributes.ContentState(
currentHealthLevel: hero.healthLevel,
eventDescription: "Adventure has begun!"
)
let activity = try Activity.request(
attributes: adventure,
content: .init(state: initialState, staleDate: nil),
pushType: .token
)
Task {
for await pushToken in activity.pushTokenUpdates {
let pushTokenString = pushToken.reduce("") { $0 + String(format: "%02x", $1) }
Logger().log("New push token: \(pushTokenString)")
try await self.sendPushToken(hero: hero, pushTokenString: pushTokenString)
}
}
} APNs push payload: Updating
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"content-state": {
"currentHealthLevel": 0.941,
"eventDescription": "Power Panda found a sword!"
}
}
} Printing content state JSON
let contentState = AdventureAttributes.ContentState(
currentHealthLevel: 0.941,
eventDescription: "Power Panda found a sword!"
)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try! encoder.encode(contentState)
Logger().log("\(String(data: json, encoding: .utf8)!)") Terminal: Constructing an APNs request with curl
curl \
--header "apns-topic: com.example.apple-samplecode.Emoji-Rangers.push-type.liveactivity" \
--header "apns-push-type: liveactivity" \
--header "apns-priority: 10" \
--header "authorization: bearer $AUTHENTICATION_TOKEN" \
--data '{
"aps": {
"timestamp": '$(date +%s)',
"event": "update",
"content-state": {
"currentHealthLevel": 0.941,
"eventDescription": "Power Panda found a sword!"
}
}
}' \
--http2 https://api.sandbox.push.apple.com/3/device/$ACTIVITY_PUSH_TOKEN APNs push payload: Alerting
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"content-state": {
"currentHealthLevel": 0.0,
"eventDescription": "Power Panda has been knocked down!"
},
"alert": {
"title": "Power Panda is knocked down!",
"body": "Use a potion to heal Power Panda!",
"sound": "default"
}
}
} APNs push payload: Alert localization
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"content-state": {
"currentHealthLevel": 0.0,
"eventDescription": "Power Panda has been knocked down!"
},
"alert": {
"title": {
"loc-key": "%@ is knocked down!",
"loc-args": ["Power Panda"]
},
"body": {
"loc-key": "Use a potion to heal %@!",
"loc-args": ["Power Panda"]
},
"sound": "HeroDown.mp4"
}
}
} APNs push payload: Alert sound
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"content-state": {
"currentHealthLevel": 0.0,
"eventDescription": "Power Panda has been knocked down!"
},
"alert": {
"title": {
"loc-key": "%@ is knocked down!",
"loc-args": ["Power Panda"]
},
"body": {
"loc-key": "Use a potion to heal %@!",
"loc-args": ["Power Panda"]
},
"sound": "HeroDown.mp4"
}
}
} APNs push payload: Dismissal
{
"aps": {
"timestamp": 1685952000,
"event": "end",
"dismissal-date": 1685959200,
"content-state": {
"currentHealthLevel": 0.23,
"eventDescription": "Adventure over! Power Panda is taking a nap."
}
}
} APNs push payload: Stale date
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"stale-date": 1685959200,
"content-state": {
"currentHealthLevel": 0.79,
"eventDescription": "Egghead is in the woods and lost connection."
}
}
} Displaying a stale Live Activity UI
struct AdventureActivityConfiguration: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AdventureAttributes.self) { context in
AdventureLiveActivityView(
hero: context.attributes.hero,
isStale: context.isStale,
contentState: context.state
)
.activityBackgroundTint(Color.gameWidgetBackground)
} dynamicIsland: { context in
// ...
}
}
} APNs push payload: Relevance score
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"relevance-score": 100,
"content-state": {
"currentHealthLevel": 0.941,
"eventDescription": "Power Panda found a sword!"
}
}
} Resources
Related sessions
-
19 min -
17 min -
15 min -
13 min