activitykit
ActivityKit enables real-time, glanceable Live Activities displayed on iOS Lock Screens and Dynamic Islands for time-sensitive information like delivery tracking, sports scores, and workout timers. Use this skill when implementing ActivityAttributes, managing Activity lifecycle methods (request, update, end), designing Dynamic Island and Lock Screen layouts, or configuring push-to-update payloads with proper APNs topic and content-state encoding.
git clone --depth 1 https://github.com/dpearson2699/swift-ios-skills /tmp/activitykit && cp -r /tmp/activitykit/skills/activitykit ~/.claude/skills/activitykitSKILL.md
# ActivityKit
ActivityKit owns real-time, glanceable Live Activities displayed on the Lock
Screen and, on supported devices, Dynamic Island. StandBy, CarPlay, and a
paired Mac can also display Live Activities, but do not blur that core routing:
ordinary Home Screen/timeline widgets belong in `widgetkit`, and generic APNs
setup belongs in `push-notifications`. Live Activity push payload shape stays in
ActivityKit: device-token updates use `apns-push-type: liveactivity` and
`apns-topic: <bundle-id>.push-type.liveactivity`, while `aps.content-state` must
decode into the app's actual `ActivityAttributes.ContentState` `Codable` shape.
Do not assume `Date` or `ClosedRange<Date>` use Unix timestamp
`lowerBound`/`upperBound` dictionaries unless the Swift model and server
contract coordinate that encoding. Boundary answers that keep ActivityKit APNs
payloads, `content-state`, or Live Activity data contracts in scope should
include these payload-shape invariants even when routing generic APNs setup
elsewhere. Patterns target iOS 26+ with Swift 6.3;
modern `ActivityContent` lifecycle examples require iOS 16.2+ unless noted.
See [references/activitykit-patterns.md](references/activitykit-patterns.md) for complete code patterns including push payload formats, concurrent activities, state observation, and testing.
## Contents
- [Workflow](#workflow)
- [ActivityAttributes Definition](#activityattributes-definition)
- [Activity Lifecycle](#activity-lifecycle)
- [Lock Screen Presentation](#lock-screen-presentation)
- [Dynamic Island](#dynamic-island)
- [Push-to-Update](#push-to-update)
- [Recent Additions](#recent-additions)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)
## Workflow
### 1. Create a new Live Activity
1. Add `NSSupportsLiveActivities = YES` to the host app's Info.plist.
2. Define an `ActivityAttributes` struct with a nested `ContentState`.
3. Create an `ActivityConfiguration` in the widget bundle with Lock Screen
content and Dynamic Island closures.
4. Start the activity with `Activity.request(attributes:content:pushType:)`.
5. Update with `activity.update(_:)` and end with `activity.end(_:dismissalPolicy:)`.
6. Forward push tokens to your server for remote updates.
### 2. Review existing Live Activity code
Run through the Review Checklist at the end of this document.
## ActivityAttributes Definition
Define both static data (immutable for the activity lifetime) and dynamic
`ContentState` (changes with each update). Keep `ContentState` small because
the entire struct is serialized on every update and push payload.
```swift
import ActivityKit
struct DeliveryAttributes: ActivityAttributes {
// Static -- set once at activity creation, never changes
var orderNumber: Int
var restaurantName: String
// Dynamic -- updated throughout the activity lifetime
struct ContentState: Codable, Hashable {
var driverName: String
var estimatedDeliveryTime: ClosedRange<Date>
var currentStep: DeliveryStep
}
}
enum DeliveryStep: String, Codable, Hashable, CaseIterable {
case confirmed, preparing, pickedUp, delivering, delivered
var icon: String {
switch self {
case .confirmed: "checkmark.circle"
case .preparing: "frying.pan"
case .pickedUp: "bag.fill"
case .delivering: "box.truck.fill"
case .delivered: "house.fill"
}
}
}
```
### Stale Date
Set `staleDate` on `ActivityContent` to tell the system when content becomes outdated. The system sets `context.isStale` to `true` after this date; show fallback UI (e.g., "Updating...") in your views.
```swift
let content = ActivityContent(
state: state,
staleDate: Date().addingTimeInterval(300), // stale after 5 minutes
relevanceScore: 75
)
```
## Activity Lifecycle
### Starting
Use `Activity.request` to create and display a Live Activity. Pass `.token` as
the `pushType` to enable remote updates via APNs. The `ActivityContent` request
shown here requires iOS 16.2+.
```swift
let attributes = DeliveryAttributes(orderNumber: 42, restaurantName: "Pizza Place")
let state = DeliveryAttributes.ContentState(
driverName: "Alex",
estimatedDeliveryTime: Date()...Date().addingTimeInterval(1800),
currentStep: .preparing
)
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75)
do {
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token
)
print("Started activity: \(activity.id)")
} catch {
print("Failed to start activity: \(error)")
}
```
### Updating
Update the dynamic content state from the app. Use `AlertConfiguration` to
trigger a visible banner and sound alongside the update.
```swift
let updatedState = DeliveryAttributes.ContentState(
driverName: "Alex",
estimatedDeliveryTime: Date()...Date().addingTimeInterval(600),
currentStep: .delivering
)
let updatedContent = ActivityContent(
state: updatedState,
staleDate: Date().addingTimeInterval(300),
relevanceScore: 90
)
// Silent update
await activity.update(updatedContent)
// Update with an alert
await activity.update(updatedContent, alertConfiguration: AlertConfiguration(
title: "Order Update",
body: "Your driver is nearby!",
sound: .default
))
```
### Ending
End the activity when the tracked event completes. Choose a dismissal policy
to control how long the ended activity lingers on the Lock Screen.
```swift
let finalState = DeliveryAttributes.ContentState(
driverName: "Alex",
estimatedDeliveryTime: Date()...Date(),
currentStep: .delivered
)
let finalContent = ActivityContent(state: finalState, staleDate: nil, relevanceScore: 0)
// System decides when to remove (up to 4 hours)
await activity.end(finalContent, dismissalPolicy: .default)
// Remove immediately
await activity.end(finalContent, dismissalPolicy: .immediate)
// Remove after a specific time (maxDiscover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.
Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.
Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, AlarmButton stop and snooze actions, authorization, state observation, countdown widget-extension handoff, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.
Build iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store Connect experiences, size/capability constraints, NSUserActivity routing, SKOverlay promotion, App Group/keychain handoff, ephemeral notifications, location confirmation, and full-app migration. Use when creating App Clips or wiring App Clip invocation, experience configuration, or full-app handoff.
Implement App Intents for Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence on iOS. Covers AppIntent actions, AppEntity and EntityQuery models, AppShortcutsProvider phrases, IndexedEntity Spotlight indexing, WidgetConfigurationIntent, SnippetIntent, and assistant schemas. Use when exposing app actions or entities to system surfaces.
Optimize App Store product pages for search visibility and conversion. Use for App Store Optimization (ASO), keyword research, app name/subtitle/keyword-field strategy, conversion-focused descriptions and promotional text, screenshot captions and ordering, Custom Product Pages with assigned search keywords, In-App Events, Product Page Optimization tests, localized metadata, ratings/review strategy, and in-app review prompt timing with RequestReviewAction or AppStore.requestReview. Also use when routing ASO vs App Store review, privacy/ATT, or StoreKit implementation boundaries.
Prepare for App Store review and prevent rejections. Covers App Store review guidelines, app rejection reasons, PrivacyInfo.xcprivacy privacy manifest requirements, required API reason codes, in-app purchase IAP and StoreKit rules, App Store Guidelines compliance, ATT App Tracking Transparency, EU DMA Digital Markets Act, HIG compliance checklist, app submission preparation, review preparation, metadata requirements, entitlements, widgets, and Live Activities review rules. Use when preparing for App Store submission, fixing rejection reasons, auditing privacy manifests, implementing ATT consent flow, configuring StoreKit IAP, or checking HIG compliance.
Integrate on-device AI using Foundation Models framework, Core ML, and open-source LLM runtimes on Apple Silicon. Covers Foundation Models (LanguageModelSession, @Generable, @Guide, SystemLanguageModel, structured output, tool calling), Core ML (coremltools, model conversion, quantization, palettization, pruning, Neural Engine, MLTensor), MLX Swift (transformer inference, unified memory), and llama.cpp (GGUF, cross-platform LLM). Use when building tool-calling AI features, working with guided generation schemas, converting models, or running on-device inference.