Overview
Live Activities are the core user-facing feature of Sightscreen. When a user follows a live match, the app starts a Live Activity that shows ball-by-ball scoring on the Lock Screen and Dynamic Island via APNs push updates. The backend drives all content — the app renders what it receives.ActivityKit integration
A Live Activity is started withActivity.request() using pushType: .token to enable remote push updates.
Required presentations
Every Live Activity must implement 6 mandatory presentations. Missing any of these causes a build-time or review rejection.| Presentation | Where it appears |
|---|---|
| Compact Leading | Dynamic Island — left side of the TrueDepth cutout |
| Compact Trailing | Dynamic Island — right side of the TrueDepth cutout |
| Minimal | Dynamic Island — when multiple activities are active, shows a small circular view |
| Expanded | Dynamic Island — long-press or when the activity is the primary one |
| Lock Screen | Lock Screen banner below the clock |
Watch/CarPlay .small | Apple Watch and CarPlay |
The compact leading and trailing views appear together when only one Live Activity is running. If multiple are active, iOS picks one for the Minimal presentation and shows the other in Compact.
Lifecycle
Request
App calls
Activity.request() with initial attributes and content state. System may reject if limits are exceeded.Active
Activity is visible on Lock Screen and Dynamic Island. Backend sends APNs pushes to update content state.
Update
Each push replaces the content state. The app re-renders all 6 presentations with the new data.
End
Match ends or user unfollows. Backend sends a push with
dismissal-date, or the app calls Activity.end().Stale detection
If no push arrives for 30 minutes, the app’s stale detection mechanism ends the activity to avoid showing outdated scores.
Push token flow
Push tokens are the bridge between the app and APNs. The flow is asynchronous — you must observe the token stream, not read it synchronously.Content state contract
The backend sends APNs pushes that update the Live Activity’s content state.| Requirement | Detail |
|---|---|
| Key format | camelCase (matches Swift Codable defaults) |
| Max payload size | Under 4KB (APNs hard limit) |
| Required fields | timestamp, event, content-state |
| APNs push type | liveactivity header for updates |
APNs push types
| Push type | Purpose |
|---|---|
liveactivity | Update or end an existing Live Activity |
push-to-start | Start a Live Activity remotely without the app being open |
Key event handling
Certain events (wickets, milestones) are assigned priority 10 in the APNs push. This ensures iOS gives them prominence in the Dynamic Island with haptic feedback and expanded presentation.State management
The Live Activity service tracks several pieces of local state:| State | Storage | Purpose |
|---|---|---|
followedMatchIds | UserDefaults | Persists which matches the user is following across app launches |
activeActivityId | In-memory | Maps match IDs to active Activity instances |
teamIntents | In-memory | Tracks which team the user is interested in for a given match |
pendingUnfollows | In-memory | Queues unfollow actions that need to sync with the backend |
followedMatchIds is stored in UserDefaults (not Keychain) because it needs to survive app restarts but is not sensitive data.Stale detection
If a Live Activity receives no push update for 30 minutes, the app considers it stale and ends it. This prevents users from seeing outdated scores if the backend stops pushing (e.g., API outage, match abandoned). The stale check runs:- On app foreground
- On a periodic background task
- When a new push arrives (resets the timer for that activity)