# Getting started: Push Notifications in Swift This guide will get you started with Ably Push Notifications in a new SwiftUI application. You'll learn how to set up your `AppDelegate` to manage push notifications, register devices with Ably, publish push notifications, subscribe to channel-based push, handle incoming notifications, and implement location-based push notifications. ## Prerequisites 1. [Sign up](https://ably.com/signup) for an Ably account. 2. Create a [new app](https://ably.com/accounts/any/apps/new), and create your first API key in the **API Keys** tab of the dashboard. * Your API key needs the `publish` and `subscribe` capabilities. * Also add the `push-admin` capability if you're using the same API key to publish a push notification. In production this would more likely be a server using a different API key. 3. Add a rule to a channel so you can test sending push notifications via a channel. Select [**Rules**](https://ably.com/accounts/any/apps/any/app_namespaces) in the Ably dashboard, add a new rule and enable the **Push notifications** option. 4. Install [Xcode](https://developer.apple.com/xcode/). 5. You'll need a real iOS device to test push notifications (the simulator doesn't support APNs). 6. Set up Apple Push Notification service (APNs) certificates through the [Apple Developer Portal](https://developer.apple.com/). ### (Optional) Install Ably CLI Use the [Ably CLI](https://ably.com/docs/platform/tools/cli.md?source=llms.txt) as an additional client to quickly test Pub/Sub features and push notifications. [`ably init`](https://ably.com/docs/cli/init.md?source=llms.txt) installs the Ably CLI, authenticates, and sets the default app and API key in a single command: #### Shell ``` npx -p @ably/cli ably init ``` ### Set up APNs certificates To enable push notifications, you need to configure APNs on Apple's developer portal: 1. Go to [Apple Developer Portal](https://developer.apple.com/account/resources/certificates/list). 2. Create an App ID for your application (if you don't have one already). 3. Enable the Push Notifications capability for your App ID. 4. Create an APNs certificate and download it. 5. In the Ably dashboard, navigate to your app's **Notifications** tab. 6. Scroll to the **Push Notifications Setup** section and select **Configure Push**. 7. Follow the instructions to upload your APNs certificate. ### Create a Swift project with Xcode Create a new iOS SwiftUI project and add the Ably SDK dependency to it: - In Xcode, go to **File > Add Package Dependencies** - Enter the repository URL: https://github.com/ably/ably-cocoa - Select the latest version and add it to your target Update your project settings: 1. Select the target for your app and go to the **Signing & Capabilities** tab. 2. Make sure you've selected your development team and that a provisioning profile has been created. 3. Add the **Push Notifications** capability by clicking **+ Capability**. All further code can be added directly to your `ContentView.swift` and `AppDelegate.swift` files. ## Step 1: Set up Ably Create an `AppDelegate.swift` file and add the `AppDelegate` class which should conform to the following protocols: `UIApplicationDelegate`, `ARTPushRegistererDelegate`, `UNUserNotificationCenterDelegate`, and `CLLocationManagerDelegate`. Set up the Ably realtime client, notification center, and location manager in your `application:didFinishLaunchingWithOptions` delegate method as shown below: ### Swift ``` import Ably import UIKit import CoreLocation import UserNotifications class AppDelegate: NSObject, UIApplicationDelegate, ARTPushRegistererDelegate, UNUserNotificationCenterDelegate, CLLocationManagerDelegate { // MARK: - Properties var realtime: ARTRealtime! var locationManager: CLLocationManager! var defaultDeviceToken: String? var locationDeviceToken: String? var activatePushCallback: ((String, ARTErrorInfo?) -> ())? var activateLocationPushCallback: ((String, ARTErrorInfo?) -> ())? var locationGrantedCallback: ((Bool) -> ())? // MARK: - UIApplicationDelegate Methods func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // Initialize Ably Realtime client with push registerer delegate let clientOptions = ARTClientOptions(key: "your-api-key") clientOptions.clientId = "push-tutorial-client" clientOptions.pushRegistererDelegate = self realtime = ARTRealtime(options: clientOptions) // Set up notification delegate UNUserNotificationCenter.current().delegate = self // Setup location manager for location-based push locationManager = CLLocationManager() locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyBest // Subscribe to realtime messages on the channel subscribeToRealtime("my-first-push-channel") return true } } ``` Here you also have some properties defined to manage device tokens and callbacks for the UI which we'll use later. ## Step 2: Set up push notifications To send and receive push notifications, you need to provide `ably-cocoa` with the device token received from Apple in the `application:didRegisterForRemoteNotificationsWithDeviceToken` delegate method. You also need to request notification permissions from the user and register your device with Ably. To handle registration results, you'll implement the `ARTPushRegistererDelegate` methods. Getting device details is also useful to confirm that your device is registered correctly. Append the following code to your `AppDelegate` class: ### Swift ``` func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { defaultDeviceToken = deviceToken.map { String(format: "%02x", UInt($0)) }.joined() // Convert device token data to a hex string print("Device Token registered: \(defaultDeviceToken!)") // Use Ably's global ARTPush method to register the device token with Ably ARTPush.didRegisterForRemoteNotifications(withDeviceToken: deviceToken, realtime: realtime) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("Failed to register for remote notifications: \(error.localizedDescription)") // Use Ably's global ARTPush method to handle registration failure ARTPush.didFailToRegisterForRemoteNotificationsWithError(error, realtime: realtime) } // MARK: - Push Notifications Methods /// Request notification permissions from user func requestUserNotificationAuthorization() { // Request authorization for alerts, sounds, and badges UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in DispatchQueue.main.async { if granted { print("Notification permissions granted") } else if let error = error { print("Notification permission error: \(error.localizedDescription)") } } } } /// Activate push notifications func activatePushNotifications(_ callback: @escaping (String, ARTErrorInfo?) -> ()) { // Store callback since activation is asynchronous activatePushCallback = callback // Request notification permissions requestUserNotificationAuthorization() // Activate push notifications with Ably realtime.push.activate() print("Activating push notifications...") } /// Deactivate push notifications func deactivatePush() { realtime.push.deactivate() print("Deactivating push notifications...") } // MARK: - ARTPushRegistererDelegate Methods func didActivateAblyPush(_ error: ARTErrorInfo?) { print("Push activation: \(error?.localizedDescription ?? "Success")") // Notify UI about activation result activatePushCallback?(realtime.device.id, error) } func didDeactivateAblyPush(_ error: ARTErrorInfo?) { print("Push deactivation: \(error?.localizedDescription ?? "Success")") } ``` Now you are ready to receive push notifications. ### Handle push notifications Use `UNUserNotificationCenterDelegate` methods to receive push notifications. You've set the notification center delegate in the `application:didFinishLaunchingWithOptions` method. Add these methods to your `AppDelegate` class: #### Swift ``` // MARK: - UNUserNotificationCenterDelegate Methods /// Handle notification when app is in foreground func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { let userInfo = notification.request.content.userInfo print("Notification received in foreground: \(userInfo)") // Display notification with banner, sound, and badge completionHandler([.banner, .sound, .badge]) } /// Handle notification when user taps on the notification when the app is in the background func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo print("Notification tapped: \(userInfo)") completionHandler() } ``` ## Step 3: Subscribe to channel push notifications To subscribe your device to a channel so it can receive channel-based push notifications, add the following methods to your `AppDelegate` class: ### Swift ``` // MARK: - Subscribe to Channels /// Subscribe to realtime messages on a channel func subscribeToRealtime(_ channelName: String) { let channel = realtime.channels.get(channelName) channel.subscribe { message in print("Received message: \(message.name ?? "") - \(String(describing: message.data))") } } /// Subscribe to a channel for push notifications func subscribeToChannel(_ channelName: String) { let channel = realtime.channels.get(channelName) channel.push.subscribeDevice { error in if let error = error { print("Error subscribing to channel push: \(error.localizedDescription)") } else { print("Subscribed to push notifications on channel: \(channelName)") } } } /// Unsubscribe from a channel func unsubscribeFromChannel(_ channelName: String) { let channel = realtime.channels.get(channelName) channel.push.unsubscribeDevice { error in if let error = error { print("Error unsubscribing from channel push: \(error.localizedDescription)") } else { print("Unsubscribed from push notifications on channel: \(channelName)") } } } ``` ## Step 4: Build the UI First, in your `PushTutorialApp.swift`, add `@UIApplicationDelegateAdaptor` wrapped `appDelegate` property to your app `@main` struct and pass it to the `ContentView`: ### Swift ``` import SwiftUI @main struct PushTutorialApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView(appDelegate: appDelegate) } } } ``` Then, update your `ContentView.swift` to accept the `appDelegate` and display a few sections: ### Swift ``` import Ably import SwiftUI struct ContentView: View { let appDelegate: AppDelegate @State private var statusMessage = "Ready to start" var body: some View { NavigationStack { VStack(spacing: 0) { // Status Section (always visible at the top) StatusSection(message: $statusMessage) .padding() // Scrollable sections ScrollView { VStack(spacing: 16) { // Setup Section SetupPushSection(appDelegate: appDelegate, statusMessage: $statusMessage) // Subscribe to Channel Section ChannelSection(appDelegate: appDelegate, statusMessage: $statusMessage) } .padding() } } .navigationTitle("Push Tutorial") } } } ``` Each section is implemented as a separate SwiftUI `View` struct for better organization. Since this is not a SwiftUI tutorial, we will not go into details of each section's implementation. They are just a few buttons with some basic styling. You can add this code at the bottom of the same `ContentView.swift` file: ### Swift ``` // MARK: - Status Section struct StatusSection: View { @Binding var message: String var body: some View { VStack(alignment: .leading, spacing: 8) { Text("Status") .font(.headline) Text(message) .font(.caption) .foregroundStyle(.secondary) .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.gray.opacity(0.1)) .cornerRadius(8) } .padding() .background(Color.white) .cornerRadius(12) .shadow(radius: 2) } } // MARK: - Setup Section struct SetupPushSection: View { let appDelegate: AppDelegate @Binding var statusMessage: String var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Setup") .font(.headline) VStack(spacing: 10) { Button(action: { appDelegate.activatePushNotifications { deviceId, error in if let error = error { statusMessage = "Push activation failed: \(error.localizedDescription)" } else { statusMessage = "Push activated. Device ID: \(deviceId)" } } statusMessage = "Activating push notifications..." }) { HStack { Image(systemName: "checkmark.circle") Text("Activate Push") } .frame(maxWidth: .infinity) .padding() .background(Color.green) .foregroundStyle(.white) .cornerRadius(8) } Button(action: { appDelegate.deactivatePush() statusMessage = "Push notifications deactivated" }) { HStack { Image(systemName: "xmark.circle") Text("Deactivate Push") } .frame(maxWidth: .infinity) .padding() .background(Color.red) .foregroundStyle(.white) .cornerRadius(8) } } } .padding() .background(Color.white) .cornerRadius(12) .shadow(radius: 2) } } // MARK: - Channel Section struct ChannelSection: View { let appDelegate: AppDelegate @Binding var statusMessage: String private let channelName = "my-first-push-channel" var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Channel Subscription") .font(.headline) VStack(spacing: 10) { Button(action: { appDelegate.subscribeToChannel(channelName) statusMessage = "Subscribed to: \(channelName)" }) { HStack { Image(systemName: "checkmark.circle.fill") Text("Subscribe to Channel") } .frame(maxWidth: .infinity) .padding() .background(Color.purple) .foregroundStyle(.white) .cornerRadius(8) } Button(action: { appDelegate.unsubscribeFromChannel(channelName) statusMessage = "Unsubscribed from: \(channelName)" }) { HStack { Image(systemName: "xmark.circle.fill") Text("Unsubscribe from Channel") } .frame(maxWidth: .infinity) .padding() .background(Color.orange) .foregroundStyle(.white) .cornerRadius(8) } } } .padding() .background(Color.white) .cornerRadius(12) .shadow(radius: 2) } } ``` Build and run your app in Xcode on a real device. You will see the UI with sections to activate push notifications and subscribe to channels. ## Step 5: Publish a push notification Tap **Activate Push** and wait until the status message displays your device ID. ### Publish directly to your device Publish a push notification directly to your client ID (or device ID using `--device-id` instead of `--client-id`) via the [Ably CLI](https://ably.com/docs/platform/tools/cli.md?source=llms.txt): #### Shell ``` ably push publish --client-id push-tutorial-client \ --title "Test push" \ --body "Hello from CLI!" \ --data '{"foo":"bar","baz":"qux"}' ``` ### Publish via a channel Tap **Subscribe to Channel** in the UI, then publish a push notification to the channel using the [Ably CLI](https://ably.com/docs/platform/tools/cli.md?source=llms.txt): #### Shell ``` ably push publish --channel my-first-push-channel \ --title "Hello" \ --body "World!" \ --message '{"name":"greeting","data":"Hello World!"}' ``` ![Screenshot of the Swift push tutorial application](https://raw.githubusercontent.com/ably/docs/main/src/images/content/screenshots/getting-started/apns-swift-getting-started-guide.png) If you unsubscribe from this channel in the app's UI, you will no longer receive push notifications for that channel. Run the same command again and verify that no notification is received. To see the full list of options for publishing push notifications with the Ably CLI, run `ably push publish --help` or see the [Ably CLI push documentation](https://ably.com/docs/cli/push.md?source=llms.txt). To publish push notifications from your own server code instead of the CLI, see [Push notification publishing](https://ably.com/docs/push/publish.md?source=llms.txt). ## Step 6: Location pushes Starting from iOS 15, you can efficiently receive location requests as push notifications. To do this, you need to apply for the special entitlement on the [Apple Developer Portal](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_location_push). Add `Location (when in use)`, `Location (Always)`, `Location Push Service Extension`, and `Push Notifications` capabilities to the **Signing & Capabilities** tab in the Xcode project target settings. Add `Location Push Service Extension` target as described at the [Apple Developer Portal](https://developer.apple.com/documentation/CoreLocation/creating-a-location-push-service-extension). For simplicity, use **Automatically manage signing**, so all needed identifiers are created for you by Xcode (with XC prefix in their display name). Your Location Push Service Extension should have a bundle identifier of your app with a suffix of extension's product name (for example, `the.company.TheApp.TheExtension`). Add these methods to your `AppDelegate` class: ### Swift ``` // MARK: - Location push methods /// Enable location push monitoring func enableLocationPush(grantedCallback: @escaping (Bool) -> (), tokenCallback: @escaping (String, ARTErrorInfo?) -> ()) { // Store callbacks since location permission request is asynchronous locationGrantedCallback = grantedCallback activateLocationPushCallback = tokenCallback switch locationManager.authorizationStatus { case .authorizedAlways: // Location permissions already granted locationGrantedCallback?(true) // Activate location push monitoring activateLocationPush() print("Location push enabled") case .notDetermined: // Request location permissions from the user with 'Always' authorization needed for location pushes locationManager.requestAlwaysAuthorization() case .denied, .restricted, .authorizedWhenInUse: locationGrantedCallback?(false) print("Location permission denied or restricted") @unknown default: break } } /// Disable location push monitoring func disableLocationPush() { locationManager?.stopUpdatingLocation() print("Location push disabled") } /// Activate location push monitoring func activateLocationPush() { print("Starting monitoring location pushes...") locationManager.startMonitoringLocationPushes { deviceToken, error in guard error == nil else { return ARTPush.didFailToRegisterForLocationNotificationsWithError(error!, realtime: self.realtime) } if let deviceToken { // Convert device token data to a hex string self.locationDeviceToken = deviceToken.map { String(format: "%02x", UInt($0)) }.joined() // Provide Ably with location device token ARTPush.didRegisterForLocationNotifications(withDeviceToken: deviceToken, realtime: self.realtime) print("Location push activated with device token: \(self.locationDeviceToken!)") } } } // MARK: - CLLocationManagerDelegate Methods func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { case .authorizedAlways: // Location permissions granted, activate location push monitoring locationGrantedCallback?(true) // Activate location push monitoring activateLocationPush() print("Location services always authorized.") case .notDetermined, .authorizedWhenInUse, .restricted, .denied: // Inform UI that location permissions were not granted locationGrantedCallback?(false) print("Location services unavailable for location pushes.") break default: break } } ``` Also add this in your `ARTPushRegistererDelegate` section. It will be called after `ARTPush.didRegisterForLocationNotifications(withDeviceToken:realtime:)` completes: ### Swift ``` func didUpdateAblyPush(_ error: ARTErrorInfo?) { print("Push update: \(error?.localizedDescription ?? "Success")") if let locationDeviceToken { // Notify UI about activation result activateLocationPushCallback?(locationDeviceToken, error) } } ``` ### Receive location pushes Once you've added the location push extension to your project, Xcode gives you a default implementation of the `LocationPushService.swift` file in your extension target. Use `locationManager(_:didUpdateLocations)` delegate method to handle location updates as needed. Now add the `LocationPushSection` to your `ContentView.swift` file to enable location push from the UI: #### Swift ``` // MARK: - Location Push Section struct LocationPushSection: View { let appDelegate: AppDelegate @Binding var statusMessage: String @State var isLocationPushEnabled = false var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Location Push") .font(.headline) VStack(spacing: 10) { Button(action: { appDelegate.enableLocationPush { granted in if granted { isLocationPushEnabled = true statusMessage = "Location push authorization granted." } else { isLocationPushEnabled = false statusMessage = "Location push authorization denied or restricted." } } tokenCallback: { deviceToken, error in if let error = error { isLocationPushEnabled = false statusMessage = "Location push activation failed: \(error.localizedDescription)" } else { statusMessage = "Location push notifications activated with device token: \(deviceToken)" } } }) { HStack { Image(systemName: "mappin.circle.fill") Text("Enable Location Push") } .frame(maxWidth: .infinity) .padding() .background(Color.green) .foregroundStyle(.white) .cornerRadius(8) } Button(action: { appDelegate.disableLocationPush() isLocationPushEnabled = false statusMessage = "Location push disabled" }) { HStack { Image(systemName: "mappin.circle") Text("Disable Location Push") } .frame(maxWidth: .infinity) .padding() .background(Color.gray) .foregroundStyle(.white) .cornerRadius(8) } HStack { Image(systemName: isLocationPushEnabled ? "checkmark.circle.fill" : "xmark.circle") Text(isLocationPushEnabled ? "Location Push: Enabled" : "Location Push: Disabled") Spacer() } .font(.caption) .foregroundStyle(isLocationPushEnabled ? .green : .gray) .padding(8) .background(Color.gray.opacity(0.1)) .cornerRadius(6) } } .padding() .background(Color.white) .cornerRadius(12) .shadow(radius: 2) } } ``` Don't forget to include this section in your `ContentView` body (add it just after the `SendPushSection`): #### Swift ``` // Location Push Section LocationPushSection(appDelegate: appDelegate, statusMessage: $statusMessage) ``` Build and run your app. Enable location push from the UI and grant location permissions when prompted. Use Ably dashboard, Apple dashboard, or Ably CLI to send location push notifications to your device. You can also use the following `cURL` command to send location pushes: #### Shell ``` curl -v \ --header "authorization: bearer ${AUTHENTICATION_TOKEN}" \ --header "apns-topic: ${BUNDLE_ID}.location-query" \ --header "apns-push-type: location" \ --data '{"aps":{}}' \ --http2 https://api.development.push.apple.com:443/3/device/${DEVICE_TOKEN} ``` Replace `${BUNDLE_ID}` with your app's bundle identifier, `${AUTHENTICATION_TOKEN}` with your APNs authentication token, and `${DEVICE_TOKEN}` with the location device token you received in the console logs after enabling location push (don't confuse it with the device ID). Read how to obtain the `AUTHENTICATION_TOKEN` on [Apple Developer Portal](https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns). Verify that your app receives and handles location push notifications correctly in the `LocationPushService` class. ![Screenshot of the Swift push tutorial application with location pushes](https://raw.githubusercontent.com/ably/docs/main/src/images/content/screenshots/getting-started/apns-swift-getting-started-guide-2.png) ## Next steps * Check the [push example](https://github.com/ably/ably-cocoa/tree/main/Examples/AblyPush) * Understand [token authentication](https://ably.com/docs/auth/token.md?source=llms.txt) before going to production. * Explore [push notification administration](https://ably.com/docs/push.md?source=llms.txt#push-admin) for managing devices and subscriptions. * Learn about [rules](https://ably.com/docs/channels.md?source=llms.txt#rules) for channel-based push notifications. * Read more about the [Push Admin API](https://ably.com/docs/api/realtime-sdk.md?source=llms.txt#push-admin). * Check out the [Push Notifications](https://ably.com/docs/push.md?source=llms.txt) documentation for advanced use cases. * Explore [Ably CLI push commands](https://ably.com/docs/cli/push.md?source=llms.txt) for the full list of push CLI options. You can also explore the [Ably SDK for Swift](https://github.com/ably/ably-cocoa) on GitHub, or visit the [API references](https://ably.com/docs/api/realtime-sdk.md?source=llms.txt) for additional functionality. ## Related Topics - [Web Push](https://ably.com/docs/push/getting-started/web.md?source=llms.txt): Get started with Ably Push Notifications in JavaScript. Learn how to register a service worker, activate push on your client, handle incoming notifications, and send push messages from the browser. - [FCM](https://ably.com/docs/push/getting-started/fcm.md?source=llms.txt): Get started with Ably Push Notifications in Kotlin for Android. Learn how to register for push notifications with Firebase Cloud Messaging (FCM), activate push on your client, handle incoming notifications, and send push messages. ## Documentation Index To discover additional Ably documentation: 1. Fetch [llms.txt](https://ably.com/llms.txt?source=llms.txt) for the canonical list of available pages. 2. Identify relevant URLs from that index. 3. Fetch target pages as needed. Avoid using assumed or outdated documentation paths.