Background
Welcome to Part 2 of our guide on integrating Live Activities and Dynamic Island in iOS. In Part 1, we explored the fundamentals—how to set up a Live Activity, manage its lifecycle, and design layouts that work seamlessly on the Lock Screen and Dynamic Island.
In this part, we’ll take things further by adding advanced functionality to elevate the user experience. We’ll learn how to:
- Animate updates for smoother transitions.
- Handling live activity from a push notification.
Let's get started…
Animate state updates in Live Activity
Animations in Live Activities make updates visually engaging and draw the user's attention to important changes. Starting with iOS 17, Live Activities automatically animate data updates using default animations, or we can customize them using SwiftUI animations.
Key Points About Animations in Live Activities
1. Default Animations:
- Text changes are animated with smooth transitions, like a blur effect.
- Image and SF Symbol updates include fade-in effects or other content transitions.
- When adding or removing views, the system uses fade animations.
2. Customizing Animations:
You can replace default animations with SwiftUI's built-in transitions and animations. Options include:
-
Transitions: Use effects like
.opacity
,.move(edge:)
,.slide
, or.push(from:)
. -
Content Transitions: Apply animations directly to text, such as
.contentTransition(.numericText())
for numbers. -
Custom Animations: Add
.animation(_:value:)
to views for more control.
For example, we can configure a content transition for queue position text as shown in this example:
Text("\(position)")
.font(.system(size: 36, weight: .semibold)).lineSpacing(48).foregroundColor(Color("TextPrimary"))
.contentTransition(.numericText())
.animation(.spring(duration: 0.2), value: position)
3. Animating View Updates:
To animate a view when data changes:
- Use the
.id()
modifier to associate the view with a data model that conforms to Hashable. - Apply a transition or animation based on the associated value.
- Example: When the queue position changes, the image transitions from the bottom.
struct QueueIllustration :View {
let position: Int
var imageName: String {
if position < 5 {
return "queue4"
} else if position < 9 {
return "queue3"
} else if position < 25 {
return "queue2"
} else {
return "queue1"
}
}
var body: some View {
Image(uiImage: UIImage(named: imageName)!)
.resizable().frame(width: 100, height: 100)
.scaledToFit().id(imageName)
.transition(.push(from: .bottom))
}
}
4. Limitations:
- Animations have a maximum duration of 2 seconds.
- On the devices with Always-On displays, animations are disabled to save battery life. You can use the
isLuminanceReduced
environment value to detect when the Always-On display is active. - On devices running iOS 16 or earlier, only system-defined animations like
.move
or.slide
are supported, and custom animations likewithAnimation
are ignored.
5. Disabling Animations
If multiple views in your Live Activity are updated simultaneously, consider disabling animations for less important changes to focus user attention.
To disable animations:
- Use
.transition(
.identity)
to prevent transitions. - Pass
nil
to the animation parameter
withAnimation(nil) {
// Updates without animations
}
Handling live activity from a push notification
One of the most powerful features of Live Activities is their ability to receive updates via push notifications. This enables us to keep the content of a Live Activity synchronized with real-time data, ensuring users always have the latest information without manual intervention.
With ActivityKit, we can update or end Live Activities directly from the server by using push tokens. Starting with iOS 17.2, we can even start new Live Activities using push notifications.
Push notification capability
Before we go ahead make sure you have added the push notification capability in your application target.
Getting Started with Push Tokens
Push tokens are the key to communicating with Live Activities on a user’s device.
1. Push-to-start tokens
A push-to-start token is a secure identifier generated by the system, which enables the server to start a new Live Activity remotely via Apple Push Notification service (APNs). This token is device-specific and tied to the user’s current app state. When the server sends a valid payload using this token, the system creates a Live Activity on the user’s device.
We can use the pushToStartTokenUpdates
asynchronous API provided by ActivityKit to listen for and retrieve push-to-start tokens. This API notifies our app whenever a new token is available.
Here’s how to implement it:
func observePushToStartToken() {
if #available(iOS 17.2, *), areActivitiesEnabled() {
Task {
for await data in Activity<WaitTimeDemoAttributes>.pushToStartTokenUpdates {
let token = data.map {String(format: "%02x", $0)}.joined()
// Send token to the server
}
}
}
}
Activity<MyAttributes>.pushToStartTokenUpdates
: an asynchronous sequence that continuously listens for new push-to-start tokens generated by the system.
2. Push-to-Update token
Once we have started a Live Activity on the user's device, we might need to update it periodically, such as to reflect progress, changes in status, or updated data. This is where push token updates come into play. When the app starts a Live Activity, it will receive a push token from ActivityKit, which we can use to send updates to the Live Activity via push notifications.
The pushTokenUpdates
API is used to retrieve the push token required for sending updates to an existing Live Activity. This token is a unique identifier for each Live Activity that the app can use to send push notifications to the device.
Here's how to get an update token:
func observePushUpdateToken() {
// Check if iOS version 17.2 or later is available and if activities are enabled
if #available(iOS 17.2, *), areActivitiesEnabled() {
// Start a task to observe updates from ActivityKit
Task {
// Listen for any updates from a Live Activity with WaitTimeDemoAttributes
for await activityData in Activity<WaitTimeDemoAttributes>.activityUpdates {
// Listen for updates to the push token associated with the Live Activity
Task {
// Iterate through the push token updates
for await tokenData in activityData.pushTokenUpdates {
// Convert the token data to a hexadecimal string
let token = tokenData.map { String(format: "%02x", $0) }.joined()
// Obtain the associated booking ID from the activity attributes
let bookingId = activityData.attributes.bookingId
// Prepare the data dictionary to pass to the callback
let data = [
"token": token,
"bookingId": activityData.attributes.bookingId
]
// TODO Send data to the server
}
}
}
}
}
}
After obtaining the update push token, we can use it to send push notifications to update the Live Activity. The token may change over time, so the server needs to be prepared to handle updates to the push token. Whenever a new token is received, it should be updated on the server and invalidated on previous tokens.
The format of the push notification is similar to the one we used to start a Live Activity, but this time, we're sending an update to the content or state of the existing Live Activity.
Using Push-to-Start Tokens to Start a Live Activity
1. Payload for Starting a Live Activity
When the server needs to start a Live Activity, it sends a JSON payload to APNs, using the push-to-start token as an identifier. The system will create the Live Activity on the device upon receiving this payload.
Payloads(all fields are required):
"aps": {
"timestamp": '$(date +%s)',
"event": "start",
"content-state": {
"progress": 0.1,
"currentPositionInQueue": 8
},
"attributes-type": "WaitTimeDemoAttributes",
"attributes": {
"waitlistName": "For Testing",
"waitlistId": "",
"bookingId": ""
},
"alert": {
"title": "",
"body": "",
"sound": "default"
}
}
-
timestamp
: Represents the current time in UNIX timestamp format to indicate when the event occurred. -
event
: The type of event; in this case, we’re starting a Live Activity ("start") -
content-state
: Holds information related to the current state of the activity. Note: The key should match with Live activity Attributes. -
attributes-type
: Specifies the type of attributes for the Live Activity. A Live activity data holder, which contains the state and attributes. -
attributes
: Contains the immutable data of live activity. -
alert
: Configures a notification that may appear to the user.
2. Headers for Push Notification
When sending this push notification, the following headers are required:
-
apns-topic
: The topic indicates the bundle identifier and specifies the push type for Live Activities.
apns-topic: <bundleId>.push-type.liveactivity
-
apns-push-type
: Specify that this is a push notification for a Live Activity.
apns-push-type: liveactivity
-
apns-priority
: Set the value for the priority to 5 or 10 (We'll discuss this in next section).
apns-priority: 10
-
authorization
: The bearer token used for authenticating the push notification request.
authorization: bearer $AUTHENTICATION_TOKEN
Behaviour on the Device
1. Starting the Live Activity:
When the device receives the push notification, the system automatically creates a new Live Activity using the provided attributes.
The app is woken up in the background to download any assets or perform setup tasks.
2. Token Refresh:
Once the Live Activity starts, the system generates a new push token for updates. This token should be send to server for managing future updates or ending the activity.
Sending Push Notifications to Update the Live Activity
Payloads(all fields are required):
"aps": {
"timestamp": '$(date +%s)',
"event": "update",
"content-state": {
"progress": 0.941,
"currentPositionInQueue": 10
}
"alert": {
"title": "",
"body": " ",
"sound": "anysound.mp4"
}
}
Once we have the payload and headers set up, we'll send the push notification from the server to APNs, which will then deliver it to the user’s device. When the device receives the update notification, ActivityKit will apply the changes to the existing Live Activity and update the content on the Dynamic Island or Lock Screen.
Once a Live Activity has ended, the system ignores any further push notifications sent to that activity. This means if we send an update after the Live Activity is complete, the system won't process it, and the information may no longer be relevant. If a push notification is not delivered or is ignored after the activity ends, the Live Activity might display outdated or incorrect information.
Sending Push Notifications to End the Live Activity
To end a Live Activity, we can send a push notification with the event field set to end. This marks the activity as complete.
By default, a Live Activity stays on the Lock Screen for up to four hours after it ends, giving users a chance to refer to the latest information. However, we can control when the Live Activity disappears by adding a dismissal-date
field in the push notification's payload.
For example, in a waitlist scenario, when the waitlist is over and users are served, we can send an update with an "end" event, like this:
{
"aps": {
"timestamp": 1685952000,
"event": "end",
"dismissal-date": 1685959200,
"content-state": {
"progress": 0.1,
"currentPositionInQueue": 1
}
}
}
To remove Live Activity immediately after it ends, set the dismissal-date
to a timestamp in the past. For example: "dismissal-date":
1663177260. Alternatively, we can set a custom dismissal time within a four-hour window.
If we don’t include a dismissal-date
, the system will handle the dismissal automatically, using its default behavior.
Activity relevance-score
When our app starts multiple Live Activities, we can control which one appears in the Dynamic Island by setting a relevance-score in the JSON payload.
If we don’t set a score or if all Live Activities have the same score, the system will show the first one started in the Dynamic Island. To highlight more important updates, assign a higher relevance score (e.g., 100), and for less important ones, use a lower score (e.g., 50).
Make sure to track and update relevance scores as needed to control which Live Activity appears in the Dynamic Island. Here's an example with a high relevance score of 100:
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"relevance-score": 100,
"content-state": {
....
}
}
}
This ensures that the most important Live Activity update is shown in the Dynamic Island.
Push Update frequency for Live Activities
ActivityKit push notifications come with certain limits on how frequently they can be sent to avoid overwhelming users. This limit is referred to as the ActivityKit notification budget, which restricts the number of updates our app can send per hour. To manage this budget and prevent throttling, we can adjust the priority of our push notifications using the apns-priority header.
Setting the Priority
By default, if we don’t specify a priority for push notifications, they will be sent with a priority of 10 (immediate delivery), and this will count toward ActivityKit push notification budget. If we exceed this budget, the system may throttle our notifications. To reduce the chance of hitting the limit, consider using a lower priority (priority 5) for updates that don’t require immediate attention.
Handling Frequent Updates
In some use cases, such as tracking a live sports event, we may need to update our Live Activity more frequently than usual. However, updating the Live Activity too often could quickly exhaust the notification budget, causing delays or missed updates.
To overcome this, we can enable frequent updates for our Live Activity. This allows our app to send updates at a higher frequency without hitting the budget limits. To enable this feature, we need to modify our app's Info.plist file:
Open the Info.plist file and add the following entry:
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
<true/>
This change will allow our app to send frequent ActivityKit push notifications. However, users can turn off this feature in their device settings.
Detecting and Handling Frequent Update Settings
If a user has deactivated frequent updates for the app, we can detect this and display a message asking them to turn it back on. Additionally, we can adjust our update frequency to match the user's preferences.
We can also subscribe to updates regarding the status of frequent updates via the frequentPushEnablementUpdates stream, which allows us to respond to changes in the notification settings in real-time.
In Summary
- Use priority 5 for less urgent updates to avoid hitting the notification budget.
- Use priority 10 for critical updates to ensure timely delivery.
- Enable frequent updates in the Info.plist for use cases requiring high-frequency updates, like live sports tracking.
- Respect users’ preferences for frequent updates and adjust the app accordingly.
Further, we've discussed the intricacies of setting up, configuring, and Testing Live Activity Push Notifications to create a seamless user experience.
Read the complete guide on Integrating Live Activity and Dynamic Island in iOS.
If you like what you read, be sure to hit 💖 button! — as a writer it means the world!
I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.
Happy coding!👋
Top comments (0)