Category: 07. iOS Capabilities

https://cdn3d.iconscout.com/3d/premium/thumb/shield-feature-3d-icon-png-download-10891099.png

  • MapKit

    Basic Map (Region)

    Display a map by binding a coordinate region. Import MapKit to use Map in SwiftUI.

    Syntax:

    • @State private var position: MapCameraPosition = .region(MKCoordinateRegion(...))
    • Map(position: $position)

    Example

    BasicMap.swift

    ContentView.swift

    App.swift

    import SwiftUI
    import MapKit
    
    struct BasicMap: View {
      @State private var position: MapCameraPosition = .region(
    
    MKCoordinateRegion(
      center: CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090),
      span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )
    ) var body: some View {
    Map(position: $position)
      .ignoresSafeArea()
    } }

    This example centers the map on Apple Park with a modest zoom.


    Annotations

    Add markers or custom annotations at specific coordinates.

    Syntax:

    • @State private var position: MapCameraPosition = .region(MKCoordinateRegion(...))
    • Map(position: $position) { Marker("Title", coordinate: ...) }
    • Annotation("Title", coordinate: ...) { View }

    Example

    Markers.swift

    ContentView.swift

    App.swift

    import SwiftUI
    import MapKit
    
    struct MarkersMap: View {
      @State private var position: MapCameraPosition = .region(
    
    MKCoordinateRegion(
      center: CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090),
      span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )
    ) let places = [
    ("Park", CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)),
    ("Cafe", CLLocationCoordinate2D(latitude: 37.3327, longitude: -122.0053))
    ] var body: some View {
    Map(position: $position) {
      ForEach(places, id: \.0) { name, coord in
        Marker(name, coordinate: coord)
      }
    }
    } }

    This example adds two simple markers around the region.


    Open in Maps

    Use MKMapItem to open a location or directions in the Apple Maps app.

    Syntax:

    • let item = MKMapItem(placemark:)
    • item.openInMaps(launchOptions:)

    Example

    OpenMaps.swift

    ContentView.swift

    App.swift

    import MapKit
    
    func openDirections() {
      let dest = MKMapItem(placemark: MKPlacemark(coordinate: .init(latitude: 37.3349, longitude: -122.0090)))
      dest.name = "Apple Park"
      dest.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving])
    }
  • Background URLSession

    Background URLSession

    Perform uploads and downloads that continue when your app is suspended using background URL sessions.


    Configure Background Session

    Create a background configuration with a unique identifier, then build a URLSession with a delegate.

    Syntax:

    • URLSessionConfiguration.background(withIdentifier:)
    • URLSession(configuration:delegate:delegateQueue:)

    Example

    BackgroundSession.swift

    ContentView.swift

    App.swift

    import Foundation
    
    final class BGSession: NSObject, URLSessionDownloadDelegate {
      static let shared = BGSession()
      lazy var session: URLSession = {
    
    let config = URLSessionConfiguration.background(withIdentifier: "com.example.bg")
    config.isDiscretionary = true
    return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }() func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
                  didFinishDownloadingTo location: URL) {
    // Move file from temporary location to a permanent URL
    } }

    This example sets up a background session and implements a download delegate callback.


    Start a Download or Upload

    Use downloadTask for large file downloads and uploadTask with a file URL for uploads.

    Syntax:

    • session.downloadTask(with: url).resume()
    • session.uploadTask(with: request, fromFile: url).resume()

    Example

    StartTasks.swift

    ContentView.swift

    App.swift

    import Foundation
    
    func startDownload() {
      let url = URL(string: "https://example.com/large.zip")!
      BGSession.shared.session.downloadTask(with: url).resume()
    }
    
    func startUpload(file: URL) {
      var req = URLRequest(url: URL(string: "https://example.com/upload")!)
      req.httpMethod = "POST"
      BGSession.shared.session.uploadTask(with: req, fromFile: file).resume()
    }
    

    This example kicks off background-friendly download and upload tasks.


    Handle Background Completion

    iOS will relaunch your app to deliver background session events. Bridge the completion handler in your app delegate.

    Syntax: application(_:handleEventsForBackgroundURLSession:completionHandler:)

    Example

    AppDelegate.swift

    ContentView.swift

    App.swift

    import UIKit
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
      var bgCompletionHandler: (() -> Void)?
    
      func application(_ application: UIApplication,
    
    handleEventsForBackgroundURLSession identifier: String,
    completionHandler: @escaping () -> Void) {
    bgCompletionHandler = completionHandler
    } } // Later, after URLSession finishes all events: extension BGSession: URLSessionDelegate { func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    DispatchQueue.main.async {
      (UIApplication.shared.delegate as? AppDelegate)?.bgCompletionHandler?()
      (UIApplication.shared.delegate as? AppDelegate)?.bgCompletionHandler = nil
    }
    } }
  • File System

    File System

    Work with the app sandbox using FileManager, document directories, and data read/write APIs.


    Locate Directories

    Use FileManager to find the Documents and Caches folders.

    Syntax: FileManager.default.urls(for:in:), e.g. .documentDirectory.cachesDirectory

    Example

    Directories.swift

    ContentView.swift

    App.swift

    import Foundation
    
    let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
    print("Docs:", docs)
    print("Caches:", caches)

    This example locates the sandboxed Documents and Caches directories.


    Read & Write

    Write and read text or binary data using Data and String APIs.

    Syntax:

    • try data.write(to: url)
    • let data = try Data(contentsOf: url)
    • try text.write(to: url, atomically:encoding:)

    Example

    ReadWrite.swift

    ContentView.swift

    App.swift

    import Foundation
    
    let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let file = docs.appendingPathComponent("hello.txt")
    
    // Write text
    let text = "Hello files!"
    try? text.write(to: file, atomically: true, encoding: .utf8)
    
    // Read text
    let loaded = try? String(contentsOf: file, encoding: .utf8)
    print(loaded ?? "")

    This example writes a text file in Documents and reads it back.


    Create and Remove

    Create folders with createDirectory and delete entries with removeItem.

    Syntax:

    • try FileManager.default.createDirectory(at:withIntermediateDirectories:)
    • try FileManager.default.removeItem(at:)

    Example

    CreateRemove.swift

    ContentView.swift

    App.swift

    import Foundation
    
    let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let folder = docs.appendingPathComponent("Temp")
    try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
    try? FileManager.default.removeItem(at: folder)
  • CloudKit

    Setup & Container

    Enable iCloud and CloudKit capability, then use a container and database.

    Syntax:

    • let container = CKContainer.default()
    • let db = container.privateCloudDatabase

    Example

    Setup.swift

    ContentView.swift

    App.swift

    import CloudKit
    
    let container = CKContainer.default()
    let db = container.privateCloudDatabase

    This prepares a handle to the user’s private database.


    Save a Record

    Create a CKRecord with fields and save it to the database.

    Syntax:

    • let rec = CKRecord(recordType: "Type")
    • db.save(rec) { ... }

    Example

    Save.swift

    ContentView.swift

    App.swift

    import CloudKit
    
    func addNote(title: String, body: String, completion: @escaping (Result) -> Void) {
      let rec = CKRecord(recordType: "Note")
      rec["title"] = title as CKRecordValue
      rec["body"] = body as CKRecordValue
      CKContainer.default().privateCloudDatabase.save(rec) { saved, err in
    
    if let saved = saved { completion(.success(saved)) } else { completion(.failure(err!)) }
    } }

    This example writes a simple Note record into the private database.


    Query Records

    Fetch records matching a predicate using CKQuery.

    Syntax:

    • CKQuery(recordType:predicate:)
    • db.perform(query, inZoneWith:)

    Example

    Query.swift

    ContentView.swift

    App.swift

    import CloudKit
    
    func loadNotes(completion: @escaping (Result<[CKRecord], Error>) -> Void) {
      let predicate = NSPredicate(value: true)
      let query = CKQuery(recordType: "Note", predicate: predicate)
      CKContainer.default().privateCloudDatabase.perform(query, inZoneWith: nil) { records, err in
    
    if let records = records { completion(.success(records)) } else { completion(.failure(err!)) }
    } }
  • Keychain Basics

    Keychain Basics

    Store small secrets (tokens, passwords) securely using Keychain APIs with proper access control.


    Store a Secret (Add)

    Use SecItemAdd with a dictionary describing the item.

    Syntax:

    • SecItemAdd(attrs as CFDictionary, nil)
    • class kSecClassGenericPassword
    • account/service keys.

    Example

    KeychainAdd.swift

    ContentView.swift

    App.swift

    import Security
    
    func saveToken(_ token: String) {
      let data = token.data(using: .utf8)!
      let query: [String: Any] = [
    
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrService as String: "com.example.auth",
    kSecAttrAccount as String: "session",
    kSecValueData as String: data,
    kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
    ] SecItemDelete(query as CFDictionary) // replace if exists let status = SecItemAdd(query as CFDictionary, nil) assert(status == errSecSuccess) }

    This example writes a token as a generic password, replacing any existing value for the same service/account.


    Read a Secret (Query)

    Use SecItemCopyMatching with kSecReturnData to get the stored bytes.

    Syntax: SecItemCopyMatching(query as CFDictionary, &item)

    Example

    KeychainGet.swift

    ContentView.swift

    App.swift

    import Security
    
    func loadToken() -> String? {
      let query: [String: Any] = [
    
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrService as String: "com.example.auth",
    kSecAttrAccount as String: "session",
    kSecReturnData as String: true,
    kSecMatchLimit as String: kSecMatchLimitOne
    ] var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) guard status == errSecSuccess, let data = item as? Data else { return nil } return String(data: data, encoding: .utf8) }

    This example reads the previously saved token by matching on service/account and returning the data.


    Update or Delete

    Update with SecItemUpdate passing an attributes-to-update dictionary; delete with SecItemDelete.

    Syntax:

    • SecItemUpdate(query, updates)
    • SecItemDelete(query)

    Example

    KeychainUpdate.swift

    ContentView.swift

    App.swift

    import Security
    
    func deleteToken() {
      let query: [String: Any] = [
    
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrService as String: "com.example.auth",
    kSecAttrAccount as String: "session",
    ] SecItemDelete(query as CFDictionary) }
  • App Clips

    App Clips

    Deliver a lightweight part of your app for a focused task, launched from links, NFC, QR, or Maps.


    What is an App Clip?

    An App Clip is a lightweight part of your app that launches quickly to perform a focused task.

    Users discover it via links, NFC, QR codes, or Maps.


    Setup Overview

    Create an App Clip target, configure invocation URLs and associated domains, and keep the experience small and focused.

    • Create an App Clip target in Xcode.
    • Configure invocation URLs and associated domains.
    • Provide a small size footprint and privacy-friendly flows.


    Invocation

    Handle invocation parameters (URL/NFC/QR/Maps) to perform the specific task immediately in the Clip.

    Example

    Invocation.swift

    // Handle invocation URL parameters to perform the task
    // Example: myappclip://pay?orderId=123
    

    This example demonstrates how to handle invocation URL parameters to perform a specific task, such as processing a payment.

  • Core Location

    Core Location

    Request authorization and read user location updates with CLLocationManager, respecting privacy and power.


    Authorization

    Add usage descriptions in Info.plist (e.g., NSLocationWhenInUseUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescription) and request permission at runtime.


    Getting Location Updates

    Use CLLocationManager for one-shot or continuous updates.

    Example

    Location.swift

    import CoreLocation
    
    class LocationDelegate: NSObject, CLLocationManagerDelegate {
      let manager = CLLocationManager()
      override init() {
    
    super.init()
    manager.delegate = self
    manager.requestWhenInUseAuthorization()
    manager.startUpdatingLocation()
    } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    print("Last: \(locations.last?.coordinate ?? .init())")
    } }

    This example configures a CLLocationManager, requests authorization, and prints the last known coordinate when updates arrive.

  • Background Modes & Tasks

    Background Modes & Tasks

    Declare the right background modes and schedule work with BGTaskScheduler for refresh or processing tasks.


    Enable Background Modes

    In Signing & Capabilities, add Background Modes.

    Check the modes you need (e.g., Background fetch, Background processing, Location updates, Audio, VOIP).


    Background Tasks (BGTaskScheduler)

    Use BGAppRefreshTask or BGProcessingTask to schedule work when your app is not active.

    Example

    BackgroundTasks.swift

    App.swift

    import BackgroundTasks
    
    let refreshTaskID = "com.example.myapp.refresh"
    
    func scheduleRefresh() {
      let request = BGAppRefreshTaskRequest(identifier: refreshTaskID)
      request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
      try? BGTaskScheduler.shared.submit(request)
    }
    
    func handleRefresh(task: BGAppRefreshTask) {
      scheduleRefresh() // reschedule
      task.expirationHandler = { /* cancel work */ }
      // Do lightweight refresh work here
      task.setTaskCompleted(success: true)
    }

    This example schedules a BGAppRefreshTask, registers a handler at app start, and marks the task completed after lightweight work.

  • Widgets & App Extensions

    Widget timeline

    Build a widget that shows a timeline of time entries.

    Syntax:

    • TimelineProvider protocol to define the timeline provider

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import WidgetKit
    import SwiftUI
    
    struct TimeEntry: TimelineEntry { let date: Date }
    
    struct TimeProvider: TimelineProvider {
      func placeholder(in context: Context) -> TimeEntry { .init(date: Date()) }
      func getSnapshot(in context: Context, completion: @escaping (TimeEntry) -> Void) { completion(.init(date: Date())) }
      func getTimeline(in context: Context, completion: @escaping (Timeline<TimeEntry>) -> Void) {
    
    let entries = stride(from: 0, through: 60*30, by: 60).map { offset in
      TimeEntry(date: Date().addingTimeInterval(Double(offset)))
    }
    completion(Timeline(entries: entries, policy: .atEnd))
    } } struct TimeWidgetView: View { var entry: TimeEntry var body: some View { Text(entry.date, style: .time).font(.headline) } } @main struct TimeWidget: Widget { var body: some WidgetConfiguration {
    StaticConfiguration(kind: "TimeWidget", provider: TimeProvider()) { e in TimeWidgetView(entry: e) }
    } }

    The example above shows a simple widget timeline with a placeholder, snapshot, and timeline provider.


    Share extension skeleton

    Build a share extension that allows users to share content with your app.

    Syntax:

    • ShareExtensionSkeleton struct to define the share extension skeleton

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ShareExtensionSkeleton: View {
      @State private var sharedText = ""
      var body: some View {
    
    VStack(spacing: 12) {
      Text("Incoming content")
      TextEditor(text: $sharedText).frame(minHeight: 120).border(.secondary)
      HStack { Spacer(); Button("Post") { /* handle share */ } }
    }
    .padding()
    } }

    The example above shows a simple share extension skeleton with a text editor and a post button.


    App Groups (Sharing Data with Widget)

    Enable App Groups for both the app and the widget extension, and use a shared suite to exchange small data.

    Syntax: UserDefaults(suiteName: "group.id"), write with set(_:forKey:), read with integer(forKey:).

    Checklist

    • Add capability App Groups to App target and Widget target
    • Create a group identifier, e.g. group.com.example.notes

    App (write count)

    Widget (read count)

    import Foundation
    
    func updateSharedNotesCount(_ count: Int) {
      let defaults = UserDefaults(suiteName: "group.com.example.notes")
      defaults?.set(count, forKey: "notesCount")
    }

    This example shares a notes count via App Groups: the app writes the value and the widget reads it to display.


    Widgets

    Create a Widget Extension target in Xcode, then design timelines/views using WidgetKit and SwiftUI.

    Syntax: implement TimelineProvider, define a Widget, and return a StaticConfiguration(kind:provider:content:).

    Example

    MyWidget.swift

    import WidgetKit
    import SwiftUI
    
    struct Provider: TimelineProvider {
      func placeholder(in context: Context) -> SimpleEntry { .init(date: Date()) }
      func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { completion(.init(date: Date())) }
      func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
    
    completion(Timeline(entries: &#91;.init(date: Date())], policy: .atEnd))
    } } struct SimpleEntry: TimelineEntry { let date: Date } struct MyWidgetEntryView: View { var entry: SimpleEntry var body: some View { Text(entry.date, style: .time) } } @main struct MyWidget: Widget {
    var body: some WidgetConfiguration {
      StaticConfiguration(kind: "MyWidget", provider: Provider()) { entry in
        MyWidgetEntryView(entry: entry)
      }
    }
    } }

    The example above shows a simple widget with a timeline provider and a static configuration.


    Deep Links from Widgets

    Open your app to a specific screen when the widget is tapped by providing a URL.

    Syntax: implement Link and onOpenURL.

    Example

    Widget link (View)

    App (onOpenURL)

    import SwiftUI
    
    struct NotesCountView: View {
      var body: some View {
    
    Link(destination: URL(string: "myapp://notes")!) {
      Text("Notes: \(entry.count)")
    }
    } }

    The example above shows a simple widget with a link to a specific screen and an app that handles the URL.


    Refresh & Timeline Policy

    Choose a refresh strategy and trigger reloads when data changes.

    Syntax: implement TimelineProvider and WidgetCenter.

    Example

    Timeline policy

    Trigger reload from App

    import WidgetKit
    // Update at a future date
    completion(Timeline(entries: entries, policy: .after(Date().addingTimeInterval(1800))))
    
    // Or end when entries are exhausted
    // completion(Timeline(entries: entries, policy: .atEnd))

    The example above shows a simple widget with a timeline provider and a static configuration.


    Supported Families

    Advertise which sizes your widget supports.

    Syntax: implement WidgetConfiguration and supportedFamilies.

    Example

    Families.swift

    import WidgetKit
    
    @main
    struct NotesCountWidget: Widget {
      var body: some WidgetConfiguration {
    
    StaticConfiguration(kind: "NotesCount", provider: NotesProvider()) { entry in
      NotesCountView(entry: entry)
    }
    .supportedFamilies(&#91;.systemSmall, .systemMedium])
    } }

    The example above shows a simple widget with a timeline provider and a static configuration.

  • Push Notifications

    Push Notifications

    Enable push capability, request user permission, and register with APNs to receive device tokens.


    Request Permission

    Request permission to send push notifications to the user.

    Syntax:

    • UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.badge,.sound])

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    import UserNotifications
    
    struct PushPermissionDemo: View {
      @State private var status: UNAuthorizationStatus? = nil
      var body: some View {
    
    VStack(spacing: 12) {
      Text("Status: \(statusLabel)")
      Button("Request Permission") {
        UNUserNotificationCenter.current().requestAuthorization(options: &#91;.alert,.badge,.sound]) { _, _ in
          UNUserNotificationCenter.current().getNotificationSettings { s in
            DispatchQueue.main.async { status = s.authorizationStatus }
          }
        }
      }
    }
    .padding()
    .onAppear {
      UNUserNotificationCenter.current().getNotificationSettings { s in
        DispatchQueue.main.async { status = s.authorizationStatus }
      }
    }
    } private var statusLabel: String {
    switch status { case .authorized?: return "authorized"
    case .denied?: return "denied"
    case .notDetermined?: return "notDetermined"
    case .provisional?: return "provisional"
    case .ephemeral?: return "ephemeral"
    default: return "unknown" }
    } }

    The example above shows a simple push permission request with a button to request access and a label to display the current status.


    Foreground Presentation

    Present notifications when the app is in the foreground.

    Syntax:

    • UNUserNotificationCenter.current().getNotificationSettings { s in ... }

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ForegroundPresentationDemo: View {
      @State private var last = "(none)"
      var body: some View {
    
    VStack(spacing: 12) {
      Text("Last received: \(last)")
      Button("Simulate Delivery") { last = Date().formatted() }
      Text("Tip: Use UNUserNotificationCenterDelegate in your app to choose banner/sound when active.")
        .font(.footnote)
        .foregroundStyle(.secondary)
    }
    .padding()
    } }

    The example above shows a simple foreground presentation with a button to simulate delivery and a label to display the last received notification.


    Request Authorization

    Ask the user for permission and register for remote notifications.

    Syntax:

    • UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.badge,.sound])

    Example

    App.swift

    AppDelegate.swift

    import SwiftUI
    import UserNotifications
    import UIKit
    @main
    struct MyApp: App {
      init() {
    
    UNUserNotificationCenter.current().requestAuthorization(options: &#91;.alert,.badge,.sound]) { granted, _ in
      if granted {
        DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }
      }
    }
    } var body: some Scene { WindowGroup { ContentView() } } }

    This example requests notification permission, registers with APNs, and handles success/failure callbacks for the device token.



    SwiftUI App + AppDelegate bridge

    In SwiftUI, bridge your AppDelegate so token callbacks are delivered:

    Example

    App.swift (bridge)

    import SwiftUI
    import UserNotifications
    import UIKit
    
    @main
    struct MyApp: App {
      @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
      init() {
    
    UNUserNotificationCenter.current().requestAuthorization(options: &#91;.alert,.badge,.sound]) { granted, _ in
      if granted {
        DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }
      }
    }
    UNUserNotificationCenter.current().delegate = appDelegate
    } var body: some Scene { WindowGroup { ContentView() } } }

    Handle Delivery (Foreground/Background)

    Use UNUserNotificationCenterDelegate to control presentation in foreground and handle taps:

    Example

    AppDelegate+Notifications.swift

    import UserNotifications
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
      // Foreground delivery: choose how to present when app is active
      func userNotificationCenter(_ center: UNUserNotificationCenter,
    
                              willPresent notification: UNNotification,
                              withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -&gt; Void) {
    completionHandler(&#91;.banner, .sound, .badge])
    } // User taps a notification func userNotificationCenter(_ center: UNUserNotificationCenter,
                              didReceive response: UNNotificationResponse,
                              withCompletionHandler completionHandler: @escaping () -&gt; Void) {
    // Navigate to a screen based on response.notification.request.content.userInfo
    completionHandler()
    } }

    The example above shows a simple foreground presentation with a button to simulate delivery and a label to display the last received notification.


    Categories & Actions

    Define actions (buttons) and attach categories to notifications:

    Example

    Categories.swift

    import UserNotifications
    
    let reply = UNTextInputNotificationAction(identifier: "REPLY",
    
                                          title: "Reply",
                                          options: &#91;])
    let mark = UNNotificationAction(identifier: "MARK",
                                title: "Mark Read",
                                options: &#91;.authenticationRequired])
    let category = UNNotificationCategory(identifier: "MESSAGE",
                                      actions: &#91;reply, mark],
                                      intentIdentifiers: &#91;],
                                      options: &#91;])
    UNUserNotificationCenter.current().setNotificationCategories([category]) // Include "category": "MESSAGE" in your APNs payload to use these actions.

    The example above shows a simple foreground presentation with a button to simulate delivery and a label to display the last received notification.


    Testing & Troubleshooting

    • Simulator: Cannot receive remote pushes; use a real device. You can test local notifications on Simulator.
    • Environments: Device tokens differ between sandbox and production; send to the matching APNs environment.
    • Keys/Certificates: Ensure APNs Auth Key (or certs) is valid and linked to your team; use the key ID and Team ID on your server.
    • Capabilities: Push Notifications capability enabled; for content updates, also enable Background Modes → Remote notifications.
    • Payload size: Keep payloads under APNs limits; include content-available: 1 for silent pushes.
    • Logging: Watch device logs in Xcode when registering to confirm token/registration errors.