$npx -y skills add dpearson2699/swift-ios-skills --skill activitykitImplement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback,
| 1 | # ActivityKit |
| 2 | |
| 3 | ActivityKit owns real-time, glanceable Live Activities displayed on the Lock |
| 4 | Screen and Dynamic Island. Ordinary timeline widgets belong in `widgetkit`, and |
| 5 | generic APNs setup belongs in `push-notifications`; ActivityKit owns the Live |
| 6 | Activity lifecycle and payload contract. `aps.content-state` must decode into |
| 7 | the exact `ActivityAttributes.ContentState` shape, including any coordinated |
| 8 | custom date/range encoding. Modern `ActivityContent` lifecycle examples require |
| 9 | iOS 16.2+ unless noted. |
| 10 | |
| 11 | See [references/activitykit-patterns.md](references/activitykit-patterns.md) for complete code patterns including push payload formats, concurrent activities, state observation, and testing. |
| 12 | |
| 13 | ## Contents |
| 14 | |
| 15 | - [Workflow](#workflow) |
| 16 | - [ActivityAttributes Definition](#activityattributes-definition) |
| 17 | - [Activity Lifecycle](#activity-lifecycle) |
| 18 | - [Lock Screen Presentation](#lock-screen-presentation) |
| 19 | - [Dynamic Island](#dynamic-island) |
| 20 | - [Push-to-Update](#push-to-update) |
| 21 | - [Recent Additions](#recent-additions) |
| 22 | - [Common Mistakes](#common-mistakes) |
| 23 | - [Review Checklist](#review-checklist) |
| 24 | - [References](#references) |
| 25 | |
| 26 | ## Workflow |
| 27 | |
| 28 | ### 1. Create a new Live Activity |
| 29 | |
| 30 | 1. Verify the host app capability and `NSSupportsLiveActivities = YES`. |
| 31 | 2. Define `ActivityAttributes.ContentState`; encode and decode a representative |
| 32 | fixture that matches the server payload contract. |
| 33 | 3. Create `ActivityConfiguration` and preview Lock Screen and Dynamic Island |
| 34 | states, including stale and terminal content. |
| 35 | 4. Check `ActivityAuthorizationInfo.areActivitiesEnabled`, then request and |
| 36 | observe the activity lifecycle. |
| 37 | 5. Exercise local update and every terminal end path. |
| 38 | 6. For remote updates, validate a complete reference payload before registering |
| 39 | rotating update or push-to-start tokens with the server. |
| 40 | |
| 41 | ### 2. Review existing Live Activity code |
| 42 | |
| 43 | Run through the Review Checklist at the end of this document. |
| 44 | |
| 45 | ## ActivityAttributes Definition |
| 46 | |
| 47 | Define both static data (immutable for the activity lifetime) and dynamic |
| 48 | `ContentState` (changes with each update). Keep `ContentState` small because |
| 49 | the entire struct is serialized on every update and push payload. |
| 50 | |
| 51 | ```swift |
| 52 | import ActivityKit |
| 53 | |
| 54 | struct DeliveryAttributes: ActivityAttributes { |
| 55 | // Static -- set once at activity creation, never changes |
| 56 | var orderNumber: Int |
| 57 | var restaurantName: String |
| 58 | |
| 59 | // Dynamic -- updated throughout the activity lifetime |
| 60 | struct ContentState: Codable, Hashable { |
| 61 | var driverName: String |
| 62 | var estimatedDeliveryTime: ClosedRange<Date> |
| 63 | var currentStep: DeliveryStep |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | enum DeliveryStep: String, Codable, Hashable, CaseIterable { |
| 68 | case confirmed, preparing, pickedUp, delivering, delivered |
| 69 | |
| 70 | var icon: String { |
| 71 | switch self { |
| 72 | case .confirmed: "checkmark.circle" |
| 73 | case .preparing: "frying.pan" |
| 74 | case .pickedUp: "bag.fill" |
| 75 | case .delivering: "box.truck.fill" |
| 76 | case .delivered: "house.fill" |
| 77 | } |
| 78 | } |
| 79 | } |
| 80 | ``` |
| 81 | |
| 82 | ### Stale Date |
| 83 | |
| 84 | 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. |
| 85 | |
| 86 | ```swift |
| 87 | let content = ActivityContent( |
| 88 | state: state, |
| 89 | staleDate: Date().addingTimeInterval(300), // stale after 5 minutes |
| 90 | relevanceScore: 75 |
| 91 | ) |
| 92 | ``` |
| 93 | |
| 94 | ## Activity Lifecycle |
| 95 | |
| 96 | ### Starting |
| 97 | |
| 98 | Use `Activity.request` to create and display a Live Activity. Pass `.token` as |
| 99 | the `pushType` to enable remote updates via APNs. The `ActivityContent` request |
| 100 | shown here requires iOS 16.2+. |
| 101 | |
| 102 | ```swift |
| 103 | let attributes = DeliveryAttributes(orderNumber: 42, restaurantName: "Pizza Place") |
| 104 | let state = DeliveryAttributes.ContentState( |
| 105 | driverName: "Alex", |
| 106 | estimatedDeliveryTime: Date()...Date().addingTimeInterval(1800), |
| 107 | currentStep: .preparing |
| 108 | ) |
| 109 | let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75) |
| 110 | |
| 111 | do { |
| 112 | let activity = try Activity.request( |
| 113 | attributes: attributes, |
| 114 | content: content, |
| 115 | pushType: .token |
| 116 | ) |
| 117 | print("Started activity: \(activity.id)") |
| 118 | } catch { |
| 119 | print("Failed to start activity: \(error)") |
| 120 | } |
| 121 | ``` |
| 122 | |
| 123 | ### Updating |
| 124 | |
| 125 | Update the dynamic content state from the app. Use `AlertConfiguration` to |
| 126 | trigger a visible banner and sound alongside the update. |
| 127 | |
| 128 | ```swift |
| 129 | let updatedState = DeliveryAttributes.ContentState( |
| 130 | driverN |