$npx -y skills add dpearson2699/swift-ios-skills --skill push-notificationsImplement, review, or debug push notifications in iOS/macOS apps — local notifications, remote (APNs) notifications, rich notifications, notification actions, silent pushes, and notification service/content extensions. Use when working with UNUserNotificationCenter, registering f
| 1 | # Push Notifications |
| 2 | |
| 3 | Implement, review, and debug local and remote notifications on iOS/macOS using `UserNotifications` and APNs. Covers permission flow, token registration, payload structure, foreground handling, notification actions, grouping, and rich notifications. Targets iOS 26+ with Swift 6.3, backward-compatible to iOS 16 unless noted. |
| 4 | |
| 5 | Keep adjacent domains separate: Live Activity `content-state` payloads belong in `activitykit`; PushKit/VoIP call pushes belong in `callkit`; App Clip ephemeral notification setup belongs in `app-clips`; long-running or scheduled background work after a silent push belongs in `background-processing`. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | - [Correction Reviews](#correction-reviews) |
| 10 | - [Permission Flow](#permission-flow) |
| 11 | - [APNs Registration](#apns-registration) |
| 12 | - [Local Notifications](#local-notifications) |
| 13 | - [Remote Notification Payload](#remote-notification-payload) |
| 14 | - [Notification Handling](#notification-handling) |
| 15 | - [Notification Actions and Categories](#notification-actions-and-categories) |
| 16 | - [Notification Grouping](#notification-grouping) |
| 17 | - [Common Mistakes](#common-mistakes) |
| 18 | - [Review Checklist](#review-checklist) |
| 19 | - [References](#references) |
| 20 | |
| 21 | ## Correction Reviews |
| 22 | |
| 23 | When reviewing flawed notification proposals, explicitly name the violated contract. APNs token reviews must say token registration is independent from alert authorization, upload on every `didRegister` callback, avoid local-cache-as-truth logic, never assume token length, and treat Simulator registration failure as expected while noting `.apns` files or `simctl push` can simulate delivery. Background-push reviews must say `content-available` only, `apns-push-type: background`, `apns-priority: 5`, Remote notifications background mode, low priority, throttled, not guaranteed, not every few minutes, and bounded `didReceiveRemoteNotification` returning the correct `UIBackgroundFetchResult`. Rich-notification reviews must say service extensions require `mutable-content: 1` plus an alert payload, silent pushes do not trigger them, attachments are supported on-disk files that the system validates and stores, secrets use Keychain Sharing while App Groups are for shared files/UserDefaults, communication notifications require capability + `NSUserActivityTypes` + `INInteraction` donation + `content.updating(from:)`, and every service-extension path including attachment/download failures and `serviceExtensionTimeWillExpire()` must call the content handler exactly once with original, best-attempt, or updated content. |
| 24 | |
| 25 | ## Permission Flow |
| 26 | |
| 27 | Request notification authorization before scheduling or displaying user-visible alerts, sounds, or badges. The system prompt appears only once; subsequent calls return the stored decision. APNs token registration is separate: call `registerForRemoteNotifications()` when the app needs a device token, even if the user hasn't granted alert authorization. |
| 28 | |
| 29 | ```swift |
| 30 | import UserNotifications |
| 31 | |
| 32 | @MainActor |
| 33 | func requestNotificationPermission() async -> Bool { |
| 34 | let center = UNUserNotificationCenter.current() |
| 35 | do { |
| 36 | let granted = try await center.requestAuthorization( |
| 37 | options: [.alert, .sound, .badge] |
| 38 | ) |
| 39 | return granted |
| 40 | } catch { |
| 41 | print("Authorization request failed: \(error)") |
| 42 | return false |
| 43 | } |
| 44 | } |
| 45 | ``` |
| 46 | |
| 47 | ### Checking Current Status |
| 48 | |
| 49 | Always check status before assuming permissions. The user can change settings at any time. |
| 50 | |
| 51 | ```swift |
| 52 | @MainActor |
| 53 | func checkNotificationStatus() async -> UNAuthorizationStatus { |
| 54 | let settings = await UNUserNotificationCenter.current().notificationSettings() |
| 55 | return settings.authorizationStatus |
| 56 | // .notDetermined, .denied, .authorized, .provisional, .ephemeral |
| 57 | } |
| 58 | ``` |
| 59 | |
| 60 | ### Provisional Notifications |
| 61 | |
| 62 | Provisional notifications deliver quietly to the notification center without interrupting the user. The user can then choose to keep or turn them off. Use for onboarding flows where you want to demonstrate value before asking for full permission. |
| 63 | |
| 64 | ```swift |
| 65 | // Delivers silently -- no permission prompt shown to the user |
| 66 | try await center.requestAuthorization(options: [.alert, .sound, .badge, .provisional]) |
| 67 | ``` |
| 68 | |
| 69 | ### Critical Alerts |
| 70 | |
| 71 | Critical alerts bypass Do Not Disturb and the mute switch. Requires a special entitlement from Apple (request via developer portal). Use only for health, safety, or security scenarios. |
| 72 | |
| 73 | ```swift |
| 74 | / |