dpearson2699/swift-ios-skills4 files

Push Notifications

Implement, 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 for remote notifications, handling notification payloads, setting up notification categories and actions, creating rich notification content, or debugging notification delivery. Also use when working with alerts, badges, sounds, background pushes, or user notification permissions in Swift apps.

Specification
Skill ID
dpearson2699/swift-ios-skills/push-notifications
Publisher
dpearson2699
Repository
swift-ios-skills
Installs
0
Files
4
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

dpearson2699/swift-ios-skills/push-notificationsInstalls these files
  • SKILL.md
  • evals/evals.json
  • references/notification-patterns.md
  • references/rich-notifications.md

What this skill tells the agent

Push Notifications

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.

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.

Contents

  • Correction Reviews
  • Permission Flow
  • APNs Registration
  • Local Notifications
  • Remote Notification Payload
  • Notification Handling
  • Notification Actions and Categories
  • Notification Grouping
  • Common Mistakes
  • Review Checklist
  • References

Correction Reviews

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.

Permission Flow

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.

import UserNotifications

@MainActor
func requestNotificationPermission() async -> Bool {
    let center = UNUserNotificationCenter.current()
    do {
        let granted = try await center.requestAuthorization(
            options: [.alert, .sound, .badge]
        )
        return granted
    } catch {
        print("Authorization request failed: \(error)")
        return false
    }
}

Checking Current Status

Always check status before assuming permissions. The user can change settings at any time.

@MainActor
func checkNotificationStatus() async -> UNAuthorizationStatus {
    let settings = await UNUserNotificationCenter.current().notificationSettings()
    return settings.authorizationStatus
    // .notDetermined, .denied, .authorized, .provisional, .ephemeral
}

Provisional Notifications

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.

// Delivers silently -- no permission prompt shown to the user
try await center.requestAuthorization(options: [.alert, .sound, .badge, .provisional])

Critical Alerts

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.

// Requires com.apple.developer.usernotifications.critical-alerts entitlement
try await center.requestAuthorization(
    options: [.alert, .sound, .badge, .criticalAlert]
)

Handling Denied Permissions

When the user has denied notifications, guide them to Settings with UIApplication.openSettingsURLString. Do not repeatedly prompt or nag.

APNs Registration

Use UIApplicationDelegateAdaptor to receive the device token in a SwiftUI app. The AppDelegate callbacks are the only way to receive APNs tokens.

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
        return true
    }

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        let token = deviceToken.map { String(format: "%02x", $0) }.joined()
        print("APNs token: \(token)")
        // Send token to your server
        Task { await TokenService.shared.upload(token: token) }
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("APNs registration failed: \(error.localizedDescription)")
        // Simulator can simulate pushes, but it does not register with APNs.
    }
}

Registration Order

Configure delegates and categories at launch. Then request user-notification authorization in context for visible notifications, and register with APNs whenever the app needs a device token. Do not gate APNs registration on .authorized; without alert authorization, remote notifications are delivered silently.

@MainActor
func configureNotifications() async {
    let center = UNUserNotificationCenter.current()
    let settings = await center.notificationSettings()

    if settings.authorizationStatus == .notDetermined {
        _ = await requestNotificationPermission()
    }

    // Needed for APNs token delivery and silent remote notifications.
    UIApplication.shared.registerForRemoteNotifications()
}

Token Handling

Device tokens change. Re-send the token to your server every time didRegisterForRemoteNotificationsWithDeviceToken fires, not just the first time. Do not persist tokens locally as a source of truth or assume a fixed token length.

Local Notifications

Schedule notifications directly from the device without a server. Useful for reminders, timers, and location-based alerts.

Creating Content

let content = UNMutableNotificationContent()
content.title = "Workout Reminder"
content.subtitle = "Time to move"
content.body = "You have a scheduled workout in 15 minutes."
content.sound = .default
content.badge = NSNumber(value: 1)
content.userInfo = ["workoutId": "abc123"]
content.threadIdentifier = "workouts"  // groups in notification center

Trigger Types

// Fire after a time interval (minimum 60 seconds for repeating)
let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 300, repeats: false)

// Fire at a specific date/time
var dateComponents = DateComponents()
dateComponents.hour = 8
dateComponents.minute = 30
let calendarTrigger = UNCalendarNotificationTrigger(

Use this skill in a project

Open any RiverX project, search the Skills panel for dpearson2699/swift-ios-skills/push-notifications, and install it in one click.

Use this skill