iOS Live Activities: Dynamic Lock Screen Updates | Mironsoft
AI generated
RN
native
React Native / ActivityKit
iOS Live Activities
for dynamic lock screen updates

Live Activities built on ActivityKit show ongoing status information, such as a delivery status, directly on the iOS lock screen and in the Dynamic Island. This article covers the architecture, React Native integration, and the hard limits on update frequency.

12 min read ActivityKit Dynamic Island Push Updates

1. What Live Activities do and why ActivityKit sits behind them

Live Activities are a system feature introduced with iOS 16.1 that let apps display time-bound, dynamically updating information directly on the lock screen and, on supporting hardware, in the Dynamic Island, without the user having to actively open the app. Technically, the feature is built on the ActivityKit framework, which models a running activity as a typed data object whose content can be updated repeatedly over its lifetime, while start and end are managed by the system.

The key difference from classic push notifications lies in persistence and interactivity: a Live Activity stays visibly anchored until it is explicitly ended or reaches its maximum lifetime, instead of disappearing after being tapped or dismissed like a notification. For use cases with an ongoing status, such as a delivery tracker or a live score, this is structurally the more appropriate presentation, because the user sees the current state at a glance at any time without switching apps.

2. Architecture: a WidgetKit extension as a technical prerequisite

Live Activities cannot be rendered directly from the main app, they strictly require a separate widget extension target in the Xcode project that imports both WidgetKit and ActivityKit. Inside this extension target, you define an ActivityAttributes struct that distinguishes between static data, such as an order number, and a dynamic ContentState, such as the current delivery status. Only the ContentState part can be updated during an activity's lifetime, the static attributes are fixed from the start.

The actual visual presentation is described through SwiftUI views inside the widget extension, separated for the lock screen and, if the activity should also appear there, for the three Dynamic Island states: compact, minimal, and expanded. This split means that a single business feature effectively requires four separate SwiftUI layouts to be maintained, which noticeably increases implementation effort compared to a simple in-app display.


// Shared/DeliveryActivityAttributes.swift
import ActivityKit

struct DeliveryActivityAttributes: ActivityAttributes {
  public struct ContentState: Codable, Hashable {
    var status: String
    var estimatedMinutes: Int
    var isStale: Bool
  }

  var orderNumber: String
}

3. Integration into a React Native app

Because Live Activities are a purely native iOS feature, no official React Native API exists for them, and a working integration strictly requires custom native Swift code or a community library such as react-native-live-activity, which already encapsulates exactly this bridge. In both cases, a native function called from JavaScript internally executes Activity.request(attributes:contentState:) and starts the activity with the initial data passed in from the JavaScript layer.

It is important that the ContentState data type must already be fixed in Swift at compile time, since it is defined as a typed struct in the widget extension. If the data model changes on the JavaScript side, say a new status field is added, the native Swift struct has to be updated in sync and the widget extension rebuilt. A purely JavaScript-driven, dynamic schema without any native change is not architecturally supported by Live Activities.


// ios/LiveActivityBridge.swift
import ActivityKit
import ExpoModulesCore

public class LiveActivityBridge: Module {
  public func definition() -> ModuleDefinition {
    Name("LiveActivityBridge")

    Function("startDeliveryActivity") { (orderNumber: String, status: String) in
      let attributes = DeliveryActivityAttributes(orderNumber: orderNumber)
      let initialState = DeliveryActivityAttributes.ContentState(
        status: status, estimatedMinutes: 30, isStale: false
      )
      _ = try Activity.request(
        attributes: attributes,
        content: .init(state: initialState, staleDate: nil)
      )
    }
  }
}

4. Local updates versus push updates through ActivityKit

As long as the app is active in the foreground or briefly in the background, a running activity can be updated locally via activity.update(using:) directly from Swift code, triggered by a call from the React Native layer. For updates that need to arrive while the app is fully terminated, say because the delivery status changed on the backend, a push update through a dedicated ActivityKit push token is required instead, which you subscribe to via Activity.pushTokenUpdates and forward to your own server.

The server then sends a special APNs payload with the header apns-push-type: liveactivity and an event field, signaling either update for a new state or end for ending the activity. This push infrastructure is completely separate from an app's classic notification push token, which means a backend that already sends regular push notifications needs additional logic to manage and map Live Activity push tokens for this feature.

5. Practical example: live delivery status after checkout

An obvious use case for a Magento-connected shop is delivery tracking: once an order reaches the Shipped status, the app starts a Live Activity with the tracking number as a static attribute and the current delivery state as dynamic content. Every further status change, such as In Transit, Out for Delivery, or Delivered, arrives through a server-triggered push update, without the user ever having to reopen the app to stay informed.

In practice, this means the carrier webhook or a periodic tracking sync on the backend fires an ActivityKit push on every relevant status change, addressed to the previously stored push token for that specific activity. Once the shipment reaches Delivered, the server sends an end event, which removes the Live Activity from the lock screen automatically after a short, system-defined display duration.


// app/hooks/useDeliveryActivity.ts
import { Platform } from "react-native";
import { requireNativeModule } from "expo-modules-core";

const LiveActivityBridge = requireNativeModule("LiveActivityBridge");

export function startDeliveryActivity(orderNumber: string, status: string): void {
  if (Platform.OS !== "ios") {
    return;
  }
  LiveActivityBridge.startDeliveryActivity(orderNumber, status);
}

6. Second example: a high-frequency live ticker

A second typical use case is a live ticker, for instance for sports scores or auction countdowns, where the content changes far more frequently than in a delivery tracker. This is exactly where an important practical limit shows up: Apple caps how many push updates a Live Activity can realistically process per hour through an internal budget system, which delays or drops later push deliveries if updates arrive too frequently instead of delivering them instantly.

For a live ticker with second-by-second changes, this means in practice that updates have to be deliberately batched or reduced to relevant events, such as pushing only on goals, half-time, or significant score changes rather than transmitting every tiny data change individually. Ignoring this budget risks the Live Activity on the user's device showing noticeably stale data, even though the server has long since sent more recent push updates.

7. Update frequency and lifetime limits in detail

A Live Activity has a default maximum lifetime of eight hours from its start, after which the system ends it automatically regardless of whether the underlying task has actually finished. The staleDate field on the ContentState can additionally mark when a given piece of content is considered outdated, which the system uses to visually signal to the user that no current data is available anymore, typically through a grayed-out presentation.

After the last received push update, the system extends the activity's visibility for additional time before finally removing it, currently around four hours beyond the regular eight-hour limit, unless an explicit end event was sent. These limits are deliberately imposed by the system and cannot be worked around from the app or the server, which means any feature planning around Live Activities has to account for these hard time windows from the outset.

8. Dynamic Island presentation: compact, minimal, expanded

On devices with a Dynamic Island, starting with the iPhone 14 Pro, a Live Activity appears in three additional states beyond the lock screen presentation: compact, positioned left and right of the camera cutout during normal device use, minimal, shown as a small indicator when a second activity is running at the same time, and expanded, once the user long-presses the Dynamic Island or the device is locked. Each of these three states requires its own deliberately compact SwiftUI layout inside the DynamicIsland configuration.

The limited space in the compact view, effectively only a few dozen pixels to the left and right of the camera cutout, forces radical information reduction: for delivery tracking, often only an icon and a short time indication fit there, while the full status description is reserved for the expanded view. This design constraint is not an implementation detail, it has to be factored into the feature's business design before the first line of SwiftUI code is written.

9. Limitations and testing

Live Activities are an iOS-exclusive feature, no direct equivalent exists on Android, at best comparable to the Live Updates for ongoing notifications introduced only with Android 16, which are structured quite differently. A cross-platform React Native app therefore has to treat this functionality fundamentally as an iOS-only feature and either offer a regular notification-based alternative on Android or simply omit the feature there.

For testing, a debug build in the simulator is enough for local updates, whereas push updates strictly require a real device with a correctly configured ActivityKit push entitlement, since the simulator does not issue real APNs push tokens. Before store release, Apple explicitly checks whether a Live Activity actually shows relevant, changing information and is not being misused as a pure advertising format, a point that tends to trigger more review questions than regular notifications.

Aspect Live Activity (iOS) Regular Push Notification
Visibility Persistently anchored on the lock screen and Dynamic Island Appears briefly, disappears after interaction
Maximum lifetime 8 hours, up to around 12 hours with push updates No lifetime limit, since it is a one-off event
Update mechanism Local via Activity.update or ActivityKit push Single, independent push delivery
Update frequency Capped by Apple's internal budget system Practically unlimited, but rate-limited by APNs
Platform iOS 16.1 and newer only Available on both iOS and Android

Mironsoft

React Native app development and Magento integration

A mobile app for the Magento shop that actually runs smoothly?

We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.

App Concept

Plan the architecture and feature scope of a Magento-connected app together.

Magento API Integration

Cleanly connect product catalog, cart, and checkout to the shop API.

Store Publishing

Guide the App Store and Google Play release process without pitfalls.

10. Summary

iOS Live Activities: The Essentials at a Glance

Platform

iOS 16.1 and later exclusively, no direct Android equivalent.

Technical basis

A dedicated widget extension with ActivityKit and WidgetKit, no pure JS approach.

Update paths

Local via Activity.update or remote via an ActivityKit push token.

Lifetime limit

8 hours by default, up to around 12 hours with active push updates.

11. FAQ: iOS Live Activities: The Essentials at a Glance

1From which iOS version do Live Activities work?
From iOS 16.1 onward, with the Dynamic Island presentation additionally limited to devices with a Dynamic Island starting at the iPhone 14 Pro.
2Is there an official React Native API for Live Activities?
No, integration requires custom native Swift code or a community library such as react-native-live-activity that provides the bridge.
3How long does a Live Activity run at most?
Eight hours by default from the start, with push updates extending visibility by roughly another four hours before final removal.
4How often can I update a Live Activity via push?
Apple caps the frequency through an internal budget system, and later push deliveries get delayed or dropped if updates arrive too frequently.
5Can I test Live Activities locally without my own push server?
Yes, a debug build is enough for local updates while the app is active, but push updates strictly require a real device with an ActivityKit push entitlement.
6Is there an Android equivalent to Live Activities?
No direct one, the closest comparison is the Live Updates feature introduced only with Android 16, which is structured quite differently technically and conceptually.
7Do I have to rebuild the widget extension on every data model change?
Yes, because ContentState is defined as a typed Swift struct inside the widget extension, it has to be updated in sync and recompiled whenever the schema changes.
8How do you end a Live Activity from the server side?
Through an APNs push payload with the event field set to end, sent to the push token stored for that specific activity.
9Why does the compact Dynamic Island view only fit minimal information?
Because only a few dozen pixels are available to the left and right of the camera cutout, which usually only fits an icon and a short time indication.
10Does Apple review Live Activities more strictly?
Yes, Apple explicitly checks whether the activity actually shows relevant, changing information and is not used as a pure advertising format.