# Swift Notifications

> When to activate: push notifications, UserNotifications framework, APNs, notification content extensions, rich notifications, notification scheduling

- Skill: `mattakushi432/swift-notifications` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/swift-notifications`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/swift-notifications/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/swift-notifications

---


# Push Notifications & UserNotifications

## Requesting Permission

```swift
import UserNotifications

func requestNotificationPermission() async -> Bool {
    let center = UNUserNotificationCenter.current()
    do {
        return try await center.requestAuthorization(options: [.alert, .sound, .badge])
    } catch {
        return false
    }
}

// Check current status
func notificationStatus() async -> UNAuthorizationStatus {
    await UNUserNotificationCenter.current().notificationSettings().authorizationStatus
}
```

## Scheduling Local Notifications

```swift
func scheduleReminder(title: String, body: String, at date: Date) async throws {
    let content = UNMutableNotificationContent()
    content.title = title
    content.body = body
    content.sound = .default
    content.badge = 1

    let components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: date)
    let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)

    let request = UNNotificationRequest(
        identifier: UUID().uuidString,
        content: content,
        trigger: trigger
    )
    try await UNUserNotificationCenter.current().add(request)
}

// Interval trigger (e.g., 10 seconds from now)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)

// Location trigger
let region = CLCircularRegion(center: coordinate, radius: 100, identifier: "office")
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)
```

## Registering for Remote Notifications

```swift
// AppDelegate or @UIApplicationDelegateAdaptor
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let token = deviceToken.map { String(format: "%02x", $0) }.joined()
        Task { await sendTokenToServer(token) }
    }

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("APNs registration failed: \(error)")
    }
}

// In SwiftUI lifecycle
@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    var body: some Scene {
        WindowGroup { ContentView() }
            .task { UIApplication.shared.registerForRemoteNotifications() }
    }
}
```

## Handling Notifications

```swift
extension AppDelegate: UNUserNotificationCenterDelegate {
    // Notification received while app is in foreground
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        completionHandler([.banner, .sound, .badge])
    }

    // User tapped notification
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        let userInfo = response.notification.request.content.userInfo
        handleNotificationAction(response.actionIdentifier, userInfo: userInfo)
        completionHandler()
    }
}
```

## Rich Notifications with Attachments

```swift
// Add image to notification
func scheduleRichNotification(imageURL: URL) async throws {
    let content = UNMutableNotificationContent()
    content.title = "New Photo"
    content.body = "Someone shared a photo with you"

    // Download and attach image
    let (tempURL, _) = try await URLSession.shared.download(from: imageURL)
    let attachment = try UNNotificationAttachment(
        identifier: "image",
        url: tempURL,
        options: [UNNotificationAttachmentOptionsTypeHintKey: "public.jpeg"]
    )
    content.attachments = [attachment]

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
    let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
    try await UNUserNotificationCenter.current().add(request)
}
```

## Notification Categories and Actions

```swift
// Register categories at launch
let acceptAction = UNNotificationAction(identifier: "ACCEPT", title: "Accept", options: .foreground)
let declineAction = UNNotificationAction(identifier: "DECLINE", title: "Decline", options: .destructive)

let inviteCategory = UNNotificationCategory(
    identifier: "INVITE",
    actions: [acceptAction, declineAction],
    intentIdentifiers: [],
    options: .customDismissAction
)
UNUserNotificationCenter.current().setNotificationCategories([inviteCategory])

// Set category in notification payload (APNs or local)
content.categoryIdentifier = "INVITE"
```

## Common Anti-Patterns

- **Not requesting permission before registering** — always call `requestAuthorization` first
- **Storing device tokens as String in CoreData** — tokens change; always re-register and update server
- **Not handling `didFailToRegisterForRemoteNotifications`** — APNs fails in simulators without entitlement
- **No silent notification handling** — add `content-available: 1` in APNs payload for background refresh
- **Badge count not cleared** — clear badge with `UNUserNotificationCenter.current().setBadgeCount(0)`

