Skip to content
All essays
iOSMarch 27, 202513 min

Push Notifications in Swift: Complete Implementation Guide

Implement push notifications in iOS apps. APNs, local notifications, and best practices.

Ü
Ümit Uz
Mobile & Full Stack Developer

Push notifications are essential for engaging users. Learn to implement both remote and local notifications in iOS apps.

Setup and Configuration

Enable Capabilities

  1. 1Go to Project Settings > Signing & Capabilities
  2. 2Add "Push Notifications" capability
  3. 3Add "Background Modes" capability
  4. 4Enable "Remote notifications"

Request Authorization

swift
import UserNotifications

class NotificationManager: ObservableObject {
    func requestAuthorization() async -> Bool {
        let center = UNUserNotificationCenter.current()

        do {
            let granted = try await center.requestAuthorization(
                options: [.alert, .sound, .badge]
            )
            await MainActor.run {
                UIApplication.shared.registerForRemoteNotifications()
            }
            return granted
        } catch {
            print("Authorization failed: \(error)")
            return false
        }
    }

    func getAuthorizationStatus() async -> UNAuthorizationStatus {
        let center = UNUserNotificationCenter.current()
        let settings = await center.notificationSettings()
        return settings.authorizationStatus
    }
}

Remote Notifications

Register for APNs

swift
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        Task {
            let granted = await NotificationManager().requestAuthorization()
            print("Authorization granted: \(granted)")
        }
        return true
    }

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("Device Token: \(token)")

        // Send token to your server
        Task {
            await sendDeviceTokenToServer(token)
        }
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("Failed to register: \(error)")
    }
}

Handle Incoming Notifications

swift
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable: Any]
    ) async -> UIBackgroundFetchResult {
        // Handle notification
        if let aps = userInfo["aps"] as? [String: Any] {
            print("Received notification: \(aps)")
        }

        return .newData
    }
}

Local Notifications

Schedule Notification

swift
class NotificationManager {
    func scheduleNotification(
        title: String,
        body: String,
        delay: TimeInterval = 5
    ) {
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default
        content.badge = 1

        let trigger = UNTimeIntervalNotificationTrigger(
            timeInterval: delay,
            repeats: false
        )

        let request = UNNotificationRequest(
            identifier: UUID().uuidString,
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("Error scheduling: \(error)")
            }
        }
    }

    func scheduleRepeatingNotification(
        title: String,
        body: String,
        interval: TimeInterval = 60
    ) {
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default

        let trigger = UNTimeIntervalNotificationTrigger(
            timeInterval: interval,
            repeats: true
        )

        let request = UNNotificationRequest(
            identifier: "repeating-\(UUID().uuidString)",
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request)
    }
}

Calendar Trigger

swift
extension NotificationManager {
    func scheduleDateNotification(
        title: String,
        body: String,
        date: Date,
        repeats: Bool = false
    ) {
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default

        let calendar = Calendar.current
        let components = calendar.dateComponents(
            [.year, .month, .day, .hour, .minute],
            from: date
        )

        let trigger = UNCalendarNotificationTrigger(
            dateMatching: components,
            repeats: repeats
        )

        let request = UNNotificationRequest(
            identifier: UUID().uuidString,
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request)
    }
}

Rich Notifications

Add Actions

swift
class NotificationManager {
    func setupNotificationCategories() {
        let acceptAction = UNNotificationAction(
            identifier: "ACCEPT_ACTION",
            title: "Accept",
            options: [.foreground]
        )

        let declineAction = UNNotificationAction(
            identifier: "DECLINE_ACTION",
            title: "Decline",
            options: [.destructive]
        )

        let category = UNNotificationCategory(
            identifier: "INVITATION_CATEGORY",
            actions: [acceptAction, declineAction],
            intentIdentifiers: []
        )

        UNUserNotificationCenter.current()
            .setNotificationCategories([category])
    }

    func sendInteractiveNotification(
        title: String,
        body: String
    ) {
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default
        content.categoryIdentifier = "INVITATION_CATEGORY"

        let trigger = UNTimeIntervalNotificationTrigger(
            timeInterval: 1,
            repeats: false
        )

        let request = UNNotificationRequest(
            identifier: UUID().uuidString,
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request)
    }
}

Handle Actions

swift
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        let userInfo = response.notification.request.content.userInfo

        switch response.actionIdentifier {
        case "ACCEPT_ACTION":
            handleAcceptAction(userInfo: userInfo)
        case "DECLINE_ACTION":
            handleDeclineAction(userInfo: userInfo)
        default:
            handleDefaultAction(userInfo: userInfo)
        }

        completionHandler()
    }

    private func handleAcceptAction(userInfo: [AnyHashable: Any]) {
        // Handle accept action
        print("Accepted")
    }

    private func handleDeclineAction(userInfo: [AnyHashable: Any]) {
        // Handle decline action
        print("Declined")
    }

    private func handleDefaultAction(userInfo: [AnyHashable: Any]) {
        // Handle default tap
        print("Default action")
    }
}

Notification Delegate

Foreground Notifications

swift
class NotificationManager: NSObject, ObservableObject, UNUserNotificationCenterDelegate {
    override init() {
        super.init()
        UNUserNotificationCenter.current().delegate = self
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        // Show notification when app is in foreground
        completionHandler([.banner, .sound, .badge])
    }
}

Background Notifications

Silent Notifications

swift
extension AppDelegate {
    func application(
        _ application: UIApplication,
        didReceiveRemoteNotification userInfo: [AnyHashable: Any],
        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
    ) {
        // Handle silent notification
        if let contentAvailable = userInfo["content-available"] as? Int,
           contentAvailable == 1 {
            // Fetch new data
            Task {
                await fetchNewData()
                completionHandler(.newData)
            }
        } else {
            completionHandler(.noData)
        }
    }

    private func fetchNewData() async {
        // Fetch data from server
    }
}

Best Practices

  1. 1Permission: Request permission at the right time
  2. 2Personalization: Send personalized notifications
  3. 3Timing: Respect user's time zone
  4. 4Content: Keep messages clear and concise
  5. 5Actions: Provide relevant actions
  6. 6Testing: Test thoroughly on real devices
  7. 7Opt-out: Easy opt-out option
  8. 8Analytics: Track notification performance

Push notifications, when done right, significantly improve user engagement!

Related essays

Next essay
In-App Purchases with StoreKit: Complete Guide