Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
90
90%
Does it follow best practices?
Impact
—
Average score across 248 eval scenarios
Advisory
Suggest reviewing before use
Complete implementation patterns for ActivityKit Live Activities, Dynamic
Island, push-to-update, and lifecycle management. All patterns use modern
Swift async/await and ActivityContent, so they target iOS 16.2+ unless noted.
Define the data model for your Live Activity. Static properties go on the
outer struct; dynamic properties go in ContentState.
import ActivityKit
struct RideAttributes: ActivityAttributes {
// Static -- set at creation, immutable for the activity lifetime
var riderName: String
var pickupLocation: String
var dropoffLocation: String
struct ContentState: Codable, Hashable {
var driverName: String
var driverPhoto: String // SF Symbol name or asset name
var vehicleDescription: String
var etaStartSeconds: Int
var etaEndSeconds: Int
// For server pushes, prefer scalar fields or coordinated custom Codable keys.
var status: RideStatus
var distanceRemaining: Double // miles
}
}
enum RideStatus: String, Codable, Hashable {
case driverAssigned
case driverEnRoute
case driverArrived
case inProgress
case arriving
case completed
case cancelled
case failed
}
extension RideAttributes.ContentState {
var etaRange: ClosedRange<Date> {
Date(timeIntervalSince1970: TimeInterval(etaStartSeconds))...
Date(timeIntervalSince1970: TimeInterval(etaEndSeconds))
}
}Keep ContentState lightweight. ActivityKit attributes and content-state data
must fit within the framework's 4 KB data limit. Avoid storing images, large
strings, or deeply nested objects.
import ActivityKit
@MainActor
func startRideActivity(
rider: String,
pickup: String,
dropoff: String,
driver: String,
vehicle: String
) async throws -> Activity<RideAttributes> {
// Check authorization before attempting to start
let authInfo = ActivityAuthorizationInfo()
guard authInfo.areActivitiesEnabled else {
throw RideError.liveActivitiesDisabled
}
let attributes = RideAttributes(
riderName: rider,
pickupLocation: pickup,
dropoffLocation: dropoff
)
let initialState = RideAttributes.ContentState(
driverName: driver,
driverPhoto: "car.fill",
vehicleDescription: vehicle,
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().addingTimeInterval(600).timeIntervalSince1970),
status: .driverAssigned,
distanceRemaining: 2.5
)
let content = ActivityContent(
state: initialState,
staleDate: Date().addingTimeInterval(120), // stale after 2 min
relevanceScore: 80
)
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token // Enable push updates
)
// Forward push token to server for remote updates
Task {
for await token in activity.pushTokenUpdates {
let tokenString = token.map { String(format: "%02x", $0) }.joined()
try? await ServerAPI.shared.registerActivityToken(
tokenString, rideID: activity.id
)
}
}
// Observe state changes for cleanup
Task {
for await state in activity.activityStateUpdates {
if state == .dismissed {
// Activity removed from UI -- clean up local resources
RideStore.shared.removeActivity(id: activity.id)
}
}
}
return activity
}Schedule the activity to appear at a future time without the app in foreground:
let gameTime = Calendar.current.date(
from: DateComponents(year: 2026, month: 3, day: 15, hour: 19, minute: 0)
)!
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token,
style: .standard,
alertConfiguration: AlertConfiguration(
title: "Game Starting",
body: "The live score is ready.",
sound: .default
),
start: gameTime // iOS 26+
)Use .standard for persistent Live Activities that should remain visible until
the app, push, user, or system duration limit ends them. .transient is only
for short-lived expanded Dynamic Island presentations that can auto-end when the
user locks the device, collapses or shrinks the expanded presentation, leaves
the app, or does other work outside Dynamic Island; it is wrong for persistent
Live Activities.
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token,
style: .standard
)func updateRideActivity(
_ activity: Activity<RideAttributes>,
newStatus: RideStatus,
eta: ClosedRange<Date>,
distance: Double,
showAlert: Bool = false
) async {
let updatedState = RideAttributes.ContentState(
driverName: activity.content.state.driverName,
driverPhoto: activity.content.state.driverPhoto,
vehicleDescription: activity.content.state.vehicleDescription,
etaStartSeconds: Int(eta.lowerBound.timeIntervalSince1970),
etaEndSeconds: Int(eta.upperBound.timeIntervalSince1970),
status: newStatus,
distanceRemaining: distance
)
let content = ActivityContent(
state: updatedState,
staleDate: Date().addingTimeInterval(120),
relevanceScore: newStatus == .driverArrived ? 100 : 80
)
if showAlert {
await activity.update(content, alertConfiguration: AlertConfiguration(
title: "Ride Update",
body: alertMessage(for: newStatus),
sound: .default
))
} else {
await activity.update(content)
}
}
private func alertMessage(for status: RideStatus) -> String {
switch status {
case .driverArrived: "Your driver has arrived!"
case .arriving: "You're almost there!"
case .completed: "You've arrived at your destination."
default: "Your ride status has changed."
}
}{
"aps": {
"timestamp": 1700000000,
"event": "update",
"content-state": {
"driverName": "Maria",
"driverPhoto": "car.fill",
"vehicleDescription": "White Toyota Camry",
"etaStartSeconds": 1700000000,
"etaEndSeconds": 1700000300,
"status": "driverArrived",
"distanceRemaining": 0.0
},
"stale-date": 1700000300,
"relevance-score": 100,
"alert": {
"title": "Ride Update",
"body": "Your driver has arrived!",
"sound": "default"
}
}
}{
"aps": {
"timestamp": 1700002000,
"event": "end",
"dismissal-date": 1700005600,
"content-state": {
"driverName": "Maria",
"driverPhoto": "car.fill",
"vehicleDescription": "White Toyota Camry",
"etaStartSeconds": 1700002000,
"etaEndSeconds": 1700002000,
"status": "completed",
"distanceRemaining": 0.0
}
}
}Send to the push-to-start token to remotely create an activity. The alert field is required for push-to-start:
{
"aps": {
"timestamp": 1700000000,
"event": "start",
"attributes-type": "RideAttributes",
"attributes": {
"riderName": "Jordan",
"pickupLocation": "123 Main St",
"dropoffLocation": "456 Oak Ave"
},
"content-state": {
"driverName": "Maria",
"driverPhoto": "car.fill",
"vehicleDescription": "White Toyota Camry",
"etaStartSeconds": 1700000000,
"etaEndSeconds": 1700000600,
"status": "driverAssigned",
"distanceRemaining": 3.2
},
"alert": {
"title": "Ride Matched",
"body": "Maria is on the way in a White Toyota Camry."
}
}
}| Header | Value |
|---|---|
apns-push-type | liveactivity |
apns-topic | <bundle-id>.push-type.liveactivity |
apns-priority | 5 (lower priority) or 10 (immediate, counts against budget) |
authorization | bearer <jwt> (token auth) or use certificate auth |
The aps.alert payload controls visible alert/banner/sound behavior; priority
alone does not create an alert. The content-state JSON must decode into
ActivityAttributes.ContentState. Use the default synthesized Codable key and
value shape unless the Swift model declares custom CodingKeys; then coordinate
those exact keys and value shapes server-side. Do not assume Date or
ClosedRange<Date> values are Unix timestamp dictionaries unless your Swift
model explicitly encodes them that way. A type mismatch (e.g., sending a string
where a number is expected) can prevent ActivityKit from applying the update.
Use channel-based push only with a valid APNs-created channel ID. Enable the broadcast capability outside Xcode, have the server create the channel, and pass that channel ID to the app:
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .channel(channelIDFromServer)
)Channel pushes can update or end Live Activities, but cannot start them. Use
apns-channel-id and expiration for channel requests instead of the device-token
apns-topic header.
func endRideActivity(
_ activity: Activity<RideAttributes>,
finalStatus: RideStatus
) async {
let finalState = RideAttributes.ContentState(
driverName: activity.content.state.driverName,
driverPhoto: activity.content.state.driverPhoto,
vehicleDescription: activity.content.state.vehicleDescription,
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().timeIntervalSince1970),
status: finalStatus,
distanceRemaining: 0
)
let content = ActivityContent(state: finalState, staleDate: nil, relevanceScore: 0)
switch finalStatus {
case .completed:
// Keep on Lock Screen for 1 hour so user can review trip details
await activity.end(content, dismissalPolicy: .after(
Date().addingTimeInterval(3600)
))
case .cancelled:
// Remove immediately -- no useful info to show
await activity.end(content, dismissalPolicy: .immediate)
default:
// Let the system decide
await activity.end(content, dismissalPolicy: .default)
}
}When reviewing duration claims, distinguish the active lifetime (up to 8 hours
unless the app or user ends it sooner), system-ended Lock Screen presence (up to
4 additional hours, for 12 hours total from start), and app-ended .default
dismissal linger (up to 4 hours after ending).
When a server reports that the tracked event failed or can no longer be represented accurately, publish a terminal state and end the activity instead of leaving stale progress visible.
func handleTerminalServerFailure(
_ activity: Activity<RideAttributes>,
message: String
) async {
let failedState = RideAttributes.ContentState(
driverName: activity.content.state.driverName,
driverPhoto: activity.content.state.driverPhoto,
vehicleDescription: message,
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().timeIntervalSince1970),
status: .failed,
distanceRemaining: 0
)
let content = ActivityContent(state: failedState, staleDate: nil, relevanceScore: 0)
await activity.end(content, dismissalPolicy: .immediate)
}func endAllRideActivities() async {
for activity in Activity<RideAttributes>.activities {
await activity.end(nil, dismissalPolicy: .immediate)
}
}struct RideActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: RideAttributes.self) { context in
// Lock Screen presentation
RideLockScreenView(context: context)
} dynamicIsland: { context in
DynamicIsland {
// EXPANDED: shown on long-press
DynamicIslandExpandedRegion(.leading) {
VStack(alignment: .leading) {
Image(systemName: context.state.driverPhoto)
.font(.title2)
Text(context.state.driverName)
.font(.caption2)
.lineLimit(1)
}
}
DynamicIslandExpandedRegion(.trailing) {
VStack(alignment: .trailing) {
Text(timerInterval: context.state.etaRange, countsDown: true)
.font(.title3.monospacedDigit())
Text(String(format: "%.1f mi", context.state.distanceRemaining))
.font(.caption2)
.foregroundStyle(.secondary)
}
}
DynamicIslandExpandedRegion(.center) {
Text(context.state.status.displayName)
.font(.headline)
.lineLimit(1)
}
DynamicIslandExpandedRegion(.bottom) {
VStack {
ProgressView(
value: context.state.status.progress,
total: 1.0
)
.tint(.green)
HStack {
Label(context.attributes.pickupLocation,
systemImage: "mappin.circle.fill")
Spacer()
Label(context.attributes.dropoffLocation,
systemImage: "flag.checkered")
}
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
} compactLeading: {
// COMPACT LEADING: tiny icon identifying the activity
Image(systemName: context.state.driverPhoto)
.foregroundStyle(.green)
} compactTrailing: {
// COMPACT TRAILING: one key value
Text(timerInterval: context.state.etaRange, countsDown: true)
.frame(width: 44)
.monospacedDigit()
} minimal: {
// MINIMAL: shown when multiple activities compete
Image(systemName: "car.fill")
.foregroundStyle(.green)
}
.keylineTint(.green)
}
}
}struct RideLockScreenView: View {
let context: ActivityViewContext<RideAttributes>
var body: some View {
VStack {
// Header
HStack {
VStack(alignment: .leading) {
Text(context.state.status.displayName)
.font(.headline)
Text(context.state.vehicleDescription)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
// Live countdown timer (auto-updating, no code needed)
Text(timerInterval: context.state.etaRange, countsDown: true)
.font(.title2.monospacedDigit().bold())
.foregroundStyle(.green)
}
if context.isStale {
Label("Checking for updates...",
systemImage: "arrow.trianglehead.2.clockwise")
.font(.caption)
.foregroundStyle(.secondary)
}
// Progress bar
ProgressView(value: context.state.status.progress, total: 1.0)
.tint(.green)
// Route
HStack {
VStack(alignment: .leading) {
Text("Pickup").font(.caption2).foregroundStyle(.secondary)
Text(context.attributes.pickupLocation).font(.caption).lineLimit(1)
}
Spacer()
Image(systemName: "arrow.right")
.font(.caption)
.foregroundStyle(.secondary)
Spacer()
VStack(alignment: .trailing) {
Text("Dropoff").font(.caption2).foregroundStyle(.secondary)
Text(context.attributes.dropoffLocation).font(.caption).lineLimit(1)
}
}
}
.padding()
}
}An app can run multiple Live Activities simultaneously (system limit applies).
Track them by storing references or querying Activity<T>.activities.
@Observable
@MainActor
final class ActivityManager {
private(set) var activeDeliveries: [String: Activity<DeliveryAttributes>] = [:]
func startDelivery(orderID: String, attributes: DeliveryAttributes,
state: DeliveryAttributes.ContentState) async throws {
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75)
let activity = try Activity.request(
attributes: attributes, content: content, pushType: .token
)
activeDeliveries[orderID] = activity
// Token forwarding
Task { [weak self] in
for await token in activity.pushTokenUpdates {
let tokenString = token.map { String(format: "%02x", $0) }.joined()
try? await ServerAPI.shared.registerActivityToken(tokenString, orderID: orderID)
}
self?.activeDeliveries.removeValue(forKey: orderID)
}
}
func updateDelivery(orderID: String, state: DeliveryAttributes.ContentState) async {
guard let activity = activeDeliveries[orderID] else { return }
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 80)
await activity.update(content)
}
func endDelivery(orderID: String, finalState: DeliveryAttributes.ContentState) async {
guard let activity = activeDeliveries[orderID] else { return }
let content = ActivityContent(state: finalState, staleDate: nil, relevanceScore: 0)
await activity.end(content, dismissalPolicy: .default)
activeDeliveries.removeValue(forKey: orderID)
}
/// Reconcile in-memory state with system activities on app launch
func reconcile() {
let systemActivities = Activity<DeliveryAttributes>.activities
for activity in systemActivities {
let orderID = "\(activity.attributes.orderNumber)"
if activeDeliveries[orderID] == nil {
activeDeliveries[orderID] = activity
}
}
}
}func observeActivityState(_ activity: Activity<RideAttributes>) {
// State updates: .active, .pending, .stale, .ended, .dismissed
Task {
for await state in activity.activityStateUpdates {
switch state {
case .active:
print("Activity is visible and running")
case .pending:
// iOS 26+: scheduled but not yet displayed
print("Activity is pending start")
case .stale:
// iOS 16.2+: staleDate passed without an update
print("Content is stale -- update or end")
case .ended:
// Ended but may still be visible on Lock Screen
print("Activity ended, may still linger on Lock Screen")
case .dismissed:
// Fully removed from UI -- safe to release resources
print("Activity dismissed from Lock Screen")
cleanupResources(for: activity.id)
@unknown default:
break
}
}
}
// Content updates (observe state changes from push or other processes)
Task {
for await content in activity.contentUpdates {
print("New state: \(content.state)")
}
}
}Push tokens can change at any time. Always observe the async sequence and re-register with your server.
func observePushToken(for activity: Activity<RideAttributes>) {
Task {
for await token in activity.pushTokenUpdates {
let tokenString = token.map { String(format: "%02x", $0) }.joined()
do {
try await ServerAPI.shared.registerActivityToken(
tokenString, activityID: activity.id
)
} catch {
// Retry with exponential backoff; token is critical for updates
print("Failed to register token: \(error)")
}
}
}
}
/// Observe the ActivityKit push-to-start token for remote activity creation (iOS 17.2+).
/// This token is distinct from ordinary app/device APNs tokens and per-activity update tokens.
func observePushToStartToken() {
Task {
for await token in Activity<RideAttributes>.pushToStartTokenUpdates {
let tokenString = token.map { String(format: "%02x", $0) }.joined()
try? await ServerAPI.shared.registerPushToStartToken(tokenString)
}
}
}Always check authorization before starting an activity. The user can disable Live Activities in Settings at any time.
func checkLiveActivityAuthorization() async -> Bool {
let authInfo = ActivityAuthorizationInfo()
return authInfo.areActivitiesEnabled
}
func checkFrequentPushAuthorization() -> Bool {
ActivityAuthorizationInfo().frequentPushesEnabled
}
/// Observe authorization changes to react when user toggles the setting
func observeAuthorization() {
Task {
let authInfo = ActivityAuthorizationInfo()
for await enabled in authInfo.activityEnablementUpdates {
if enabled {
observePushToStartToken()
} else {
try? await ServerAPI.shared.disableActivityPush()
}
}
}
Task {
let authInfo = ActivityAuthorizationInfo()
for await frequentPushesEnabled in authInfo.frequentPushEnablementUpdates {
try? await ServerAPI.shared.setFrequentPushesEnabled(frequentPushesEnabled)
}
}
}func startActivitySafely(
attributes: DeliveryAttributes,
state: DeliveryAttributes.ContentState
) async {
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75)
do {
let activity = try Activity.request(
attributes: attributes, content: content, pushType: .token
)
print("Started: \(activity.id)")
} catch let error as ActivityAuthorizationError {
switch error {
case .denied:
// User disabled Live Activities in Settings
print("Live Activities disabled by user")
case .globalMaximumExceeded:
// Device-level ongoing Live Activity maximum reached
print("System-wide activity limit reached")
case .targetMaximumExceeded:
// Too many Live Activities for this app
print("App activity limit reached -- end an existing one first")
default:
print("Authorization error: \(error)")
}
} catch {
print("Unexpected error: \(error)")
}
}Live Activities continue to display when the app is backgrounded or suspended. The Live Activity UI runs in a widget extension sandbox and cannot fetch network data or receive location updates directly. Push-to-update is the primary mechanism for background updates. When the app returns to foreground, reconcile local state with the activity's current content.
@MainActor
func handleAppBecameActive() {
// Reconcile local state with live activities on foregrounding
let activities = Activity<DeliveryAttributes>.activities
for activity in activities {
switch activity.activityState {
case .active:
// Refresh from server in case pushes were missed
Task {
let serverState = try await ServerAPI.shared.fetchDeliveryState(
orderNumber: activity.attributes.orderNumber
)
let content = ActivityContent(
state: serverState,
staleDate: Date().addingTimeInterval(120),
relevanceScore: 80
)
await activity.update(content)
}
case .stale:
// Content is outdated -- update immediately
Task {
let serverState = try await ServerAPI.shared.fetchDeliveryState(
orderNumber: activity.attributes.orderNumber
)
let content = ActivityContent(
state: serverState,
staleDate: Date().addingTimeInterval(120),
relevanceScore: 80
)
await activity.update(content)
}
case .ended, .dismissed:
// Clean up local tracking
break
default:
break
}
}
}For truly background-driven updates, rely on push-to-update rather than
Background App Refresh. Push updates can arrive while the app is suspended, but
APNs delivery, priority, budget, and throttling still apply; use staleDate and
foreground reconciliation for missed updates.
The Simulator supports Live Activity rendering on the Lock Screen and displays the Dynamic Island on simulator models that include Dynamic Island. Use Xcode previews for rapid iteration:
#Preview("Lock Screen", as: .content, using: RideAttributes.preview) {
RideActivityWidget()
} contentStates: {
RideAttributes.ContentState(
driverName: "Alex",
driverPhoto: "car.fill",
vehicleDescription: "White Toyota Camry",
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().addingTimeInterval(300).timeIntervalSince1970),
status: .driverEnRoute,
distanceRemaining: 1.5
)
RideAttributes.ContentState(
driverName: "Alex",
driverPhoto: "car.fill",
vehicleDescription: "White Toyota Camry",
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().addingTimeInterval(60).timeIntervalSince1970),
status: .arriving,
distanceRemaining: 0.1
)
}
#Preview("Dynamic Island Compact", as: .dynamicIsland(.compact), using: RideAttributes.preview) {
RideActivityWidget()
} contentStates: {
RideAttributes.ContentState(
driverName: "Alex",
driverPhoto: "car.fill",
vehicleDescription: "White Toyota Camry",
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().addingTimeInterval(300).timeIntervalSince1970),
status: .driverEnRoute,
distanceRemaining: 1.5
)
}
#Preview("Dynamic Island Expanded", as: .dynamicIsland(.expanded), using: RideAttributes.preview) {
RideActivityWidget()
} contentStates: {
RideAttributes.ContentState(
driverName: "Alex",
driverPhoto: "car.fill",
vehicleDescription: "White Toyota Camry",
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().addingTimeInterval(300).timeIntervalSince1970),
status: .driverEnRoute,
distanceRemaining: 1.5
)
}
#Preview("Dynamic Island Minimal", as: .dynamicIsland(.minimal), using: RideAttributes.preview) {
RideActivityWidget()
} contentStates: {
RideAttributes.ContentState(
driverName: "Alex",
driverPhoto: "car.fill",
vehicleDescription: "White Toyota Camry",
etaStartSeconds: Int(Date().timeIntervalSince1970),
etaEndSeconds: Int(Date().addingTimeInterval(300).timeIntervalSince1970),
status: .driverEnRoute,
distanceRemaining: 1.5
)
}extension RideAttributes {
static var preview: RideAttributes {
RideAttributes(
riderName: "Jordan",
pickupLocation: "123 Main St",
dropoffLocation: "456 Oak Ave"
)
}
}Test push-to-update by sending payloads through APNs using a tool like curl
or a push notification testing app. The Simulator does not support APNs, so
push-to-update must be tested on a physical device.
# Example curl command for APNs push update (HTTP/2)
curl -v \
--http2 \
--header "apns-push-type: liveactivity" \
--header "apns-topic: com.example.app.push-type.liveactivity" \
--header "apns-priority: 10" \
--header "authorization: bearer $JWT_TOKEN" \
--data '{"aps":{"timestamp":1700000000,"event":"update","content-state":{"driverName":"Alex","driverPhoto":"car.fill","vehicleDescription":"White Toyota Camry","etaStartSeconds":1700000000,"etaEndSeconds":1700000300,"status":"driverArrived","distanceRemaining":0.0},"alert":{"title":"Driver Arrived","body":"Your driver is here!"}}}' \
https://api.push.apple.com/3/device/$DEVICE_PUSH_TOKENActivityKit log messages when activities fail to start.content-state JSON keys match the default ContentState Codable
shape or coordinated CodingKeys. Mismatches can prevent ActivityKit from
applying updates.Activity<T>.activities to inspect all running activities in the debugger.pushTokenUpdates to verify tokens are being delivered.NSSupportsLiveActivities = YES is in
the host app's Info.plist (not the widget extension's).| Key | Value | Purpose |
|---|---|---|
NSSupportsLiveActivities | YES | Enable Live Activities (required) |
NSSupportsLiveActivitiesFrequentUpdates | YES | Increase the system-managed push update budget |
Both keys belong in the host app's Info.plist, not the widget extension.
.tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references