DEV Community

Cover image for Integrating Live Activity and Dynamic Island in iOS - Part 2
Nandani Sharma for Canopas Software

Posted on • Originally published at canopas.com

Integrating Live Activity and Dynamic Island in iOS - Part 2

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)
Enter fullscreen mode Exit fullscreen mode

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))
    }
}
Enter fullscreen mode Exit fullscreen mode

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 like withAnimation 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
}
Enter fullscreen mode Exit fullscreen mode

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.

Push notification capability

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
                    }
            }

        }
    }
Enter fullscreen mode Exit fullscreen mode

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
                    }
                }
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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"
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • apns-push-type: Specify that this is a push notification for a Live Activity.
apns-push-type: liveactivity
Enter fullscreen mode Exit fullscreen mode
  • apns-priority: Set the value for the priority to 5 or 10 (We'll discuss this in next section).
apns-priority: 10
Enter fullscreen mode Exit fullscreen mode
  • authorization: The bearer token used for authenticating the push notification request.
authorization: bearer $AUTHENTICATION_TOKEN
Enter fullscreen mode Exit fullscreen mode

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"
        }
      }   

Enter fullscreen mode Exit fullscreen mode

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
         }
    }
}

Enter fullscreen mode Exit fullscreen mode

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": {
            ....
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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/>
Enter fullscreen mode Exit fullscreen mode

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)