diff --git a/src/data/nav/pubsub.ts b/src/data/nav/pubsub.ts
index 649d00dd04..a074e1d1c9 100644
--- a/src/data/nav/pubsub.ts
+++ b/src/data/nav/pubsub.ts
@@ -239,6 +239,15 @@ export default {
link: '/docs/push',
index: true,
},
+ {
+ name: 'Getting started',
+ pages: [
+ {
+ name: 'APNs',
+ link: '/docs/push/getting-started/apns',
+ },
+ ],
+ },
{
name: 'Configure and activate',
pages: [
diff --git a/src/images/content/screenshots/getting-started/apns-swift-getting-started-guide.png b/src/images/content/screenshots/getting-started/apns-swift-getting-started-guide.png
new file mode 100644
index 0000000000..885a996389
Binary files /dev/null and b/src/images/content/screenshots/getting-started/apns-swift-getting-started-guide.png differ
diff --git a/src/pages/docs/push/getting-started/apns.mdx b/src/pages/docs/push/getting-started/apns.mdx
new file mode 100644
index 0000000000..5477a35973
--- /dev/null
+++ b/src/pages/docs/push/getting-started/apns.mdx
@@ -0,0 +1,992 @@
+---
+title: "Getting started: Push Notifications in Swift"
+meta_description: "Get started with Ably Push Notifications in Swift. Learn how to register for push notifications, activate push on your client, handle incoming notifications, and send push messages."
+meta_keywords: "Push Notifications Swift, Ably Push, APNs, iOS Push, Swift push notifications, Ably Push Notifications guide, realtime push Swift, push notification example, Ably tutorial Swift, device registration, push messaging"
+---
+
+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, send 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.
+3. Your API key will need the `publish` and `subscribe` capabilities. For sending push notifications from your app, you'll also need the `push-admin` capability.
+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/).
+
+### Install Ably CLI (Optional)
+
+Use the [Ably CLI](https://github.com/ably/cli) as an additional client to quickly test Pub/Sub features and push notifications.
+
+1. Install the Ably CLI:
+
+
+```shell
+npm install -g @ably/cli
+```
+
+
+2. Run the following to log in to your Ably account and set the default app and API key:
+
+
+```shell
+ably login
+```
+
+
+### 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: "{{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
+
+ 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...")
+}
+
+/// Get current device registration details
+func getDeviceDetails(_ callback: @escaping (ARTDeviceDetails?, ARTErrorInfo?) -> ()) {
+ realtime.push.admin.deviceRegistrations.get(realtime.device.id, callback: callback)
+}
+
+// MARK: - ARTPushRegistererDelegate Methods
+
+func didActivateAblyPush(_ error: ARTErrorInfo?) {
+ print("Push activation: \(error?.localizedDescription ?? "Success")")
+ if let defaultDeviceToken {
+ // Notify UI about activation result
+ activatePushCallback?(defaultDeviceToken, error)
+ }
+}
+
+func didDeactivateAblyPush(_ error: ARTErrorInfo?) {
+ print("Push deactivation: \(error?.localizedDescription ?? "Success")")
+}
+```
+
+
+## Step 3: Receive 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()
+}
+```
+
+
+Push notifications can be sent either directly to your device ID (or client ID),
+or posted to a channel, in which case you first need to subscribe your device to that channel:
+
+
+```swift
+// MARK: - Subscribe to Channels
+
+/// 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)")
+ }
+ }
+}
+```
+
+
+Sending push notifications using device ID or client ID requires the `push-admin` capability for your API key.
+Use this method for testing purposes. In a production environment, you would typically send push notifications
+from your backend server (by posting messages with `push` `extras` field to a channel).
+
+To test push notifications in your app, you can use [Ably dashboard](https://ably.com/dashboard),
+[Apple developer dashboard](https://icloud.developer.apple.com/dashboard/) or Ably CLI.
+
+To send to your client ID using Ably CLI paste the following command into your terminal:
+
+
+```shell
+ably push publish --client-id push-tutorial-client \
+ --title "Test push" \
+ --body "Hello from CLI!" \
+ --data '{"foo":"bar","baz":"qux"}'
+```
+
+
+For sending pushes via a channel, we need some actual UI to be able to subscribe to this channel. So, let's build one.
+
+## 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"
+ @State private var selectedChannel = "exampleChannel1"
+
+ 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, selectedChannel: $selectedChannel)
+ }
+ .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 { deviceToken, error in
+ if let error = error {
+ statusMessage = "Push activation failed: \(error.localizedDescription)"
+ } else {
+ statusMessage = "Push notifications activated with device token: \(deviceToken)"
+ }
+ }
+ 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)
+ }
+
+ Button(action: {
+ appDelegate.getDeviceDetails { details, error in
+ if let details = details {
+ print("Device details: \(details)")
+ statusMessage = """
+ Device ID: \(details.id)
+ Client ID: \(details.clientId ?? "n/a")
+ Platform: \(details.platform)
+ Form Factor: \(details.formFactor)
+ """
+ } else {
+ statusMessage = "Failed to retrieve device details: \(error?.localizedDescription ?? "Unknown error")"
+ }
+ }
+ }) {
+ HStack {
+ Image(systemName: "info.circle.fill")
+ Text("Get Device Details")
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.blue)
+ .foregroundStyle(.white)
+ .cornerRadius(8)
+ }
+ }
+ }
+ .padding()
+ .background(Color.white)
+ .cornerRadius(12)
+ .shadow(radius: 2)
+ }
+}
+
+// MARK: - Channel Section
+
+// Helper to get a user-friendly title for a channel
+func titleForChannel(_ name: String) -> String {
+ let titles = [
+ "exampleChannel1": "Channel 1",
+ "exampleChannel2": "Channel 2"
+ ]
+ return titles[name] ?? name
+}
+
+struct ChannelSection: View {
+ let appDelegate: AppDelegate
+ @Binding var statusMessage: String
+ @Binding var selectedChannel: String
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 12) {
+ Text("Channel Subscription")
+ .font(.headline)
+
+ VStack(spacing: 10) {
+ HStack(spacing: 8) {
+ Menu {
+ Button(titleForChannel("exampleChannel1")) {
+ selectedChannel = "exampleChannel1"
+ }
+ Button(titleForChannel("exampleChannel2")) {
+ selectedChannel = "exampleChannel2"
+ }
+ } label: {
+ HStack {
+ Image(systemName: "line.3.horizontal.decrease.circle")
+ Text(titleForChannel(selectedChannel))
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.gray.opacity(0.2))
+ .cornerRadius(8)
+ }
+
+ Button(action: {
+ appDelegate.subscribeToChannel(selectedChannel)
+ statusMessage = "Subscribed to: \(titleForChannel(selectedChannel))"
+ }) {
+ HStack {
+ Image(systemName: "checkmark.circle.fill")
+ Text("Subscribe")
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.indigo)
+ .foregroundStyle(.white)
+ .cornerRadius(8)
+ }
+ }
+
+ Button(action: {
+ appDelegate.unsubscribeFromChannel(selectedChannel)
+ statusMessage = "Unsubscribed from: \(titleForChannel(selectedChannel))"
+ }) {
+ HStack {
+ Image(systemName: "xmark.circle.fill")
+ Text("Unsubscribe from \(titleForChannel(selectedChannel))")
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.gray)
+ .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. Tap the "Activate Push" button and wait until the status message
+displays the received device token. Try sending push using client ID or device ID as shown earlier.
+You can get your device ID from the device details button (don't confuse it with the device token):
+
+
+
+### Send push via channel
+
+To test pushes via channel, subscribe to "Channel 1" in the UI and post a message to the "exampleChannel1"
+with a `push` `extras` field using Ably CLI:
+
+
+```shell
+ably channels publish --api-key "{{API_KEY}}" exampleChannel1 '{"data":{"foo":"bar","baz":"qux"},"extras":{"push":{"notification":{"title":"Test push","body":"Hello from CLI!"}}}}'
+```
+
+
+If you unsubscribe from this channel in the app's UI, you will no longer receive push notifications for that channel.
+Send the same command again and verify that no notification is received.
+
+You can also send push notifications right from your app. The next step will show you how.
+
+## Step 5: Send pushes with code
+
+Just as you can send push notifications through the Ably CLI or dashboard, you can also send them directly from your app
+using device ID (or client ID), or channel publishing methods. For channel publishing, you don't need the admin capabilities
+for your API key.
+
+Add the following methods to your `AppDelegate` class:
+
+
+```swift
+// MARK: - Send Push Notifications
+
+/// Send push notification to a specific device ID
+func sendPushToDevice() {
+ let recipient = [
+ "deviceId": realtime.device.id
+ ]
+ let data = [
+ "notification": [
+ "title": "Push Tutorial",
+ "body": "Hello from device ID!"
+ ],
+ "data": [
+ "foo": "bar",
+ "baz": "qux"
+ ]
+ ]
+ realtime.push.admin.publish(recipient, data: data) { error in
+ print("Publish result: \(error?.localizedDescription ?? "Success")")
+ }
+}
+
+/// Send push notification to a specific client ID
+func sendPushToClientId() {
+ let recipient = [
+ "clientId": realtime.auth.clientId ?? "push-tutorial-client"
+ ]
+ let data = [
+ "notification": [
+ "title": "Push Tutorial",
+ "body": "Hello from client ID!"
+ ],
+ "data": [
+ "foo": "bar",
+ "baz": "qux"
+ ]
+ ]
+ realtime.push.admin.publish(recipient, data: data) { error in
+ print("Publish result: \(error?.localizedDescription ?? "Success")")
+ }
+}
+```
+
+
+Sending to a channel is just publishing a message on a channel with a `push` `extras` field:
+
+
+```swift
+/// Send push notification to a specific channel by publishing a message with a push extras field
+func sendPushToChannel(_ channelName: String) {
+ let message = ARTMessage(name: "example", data: "Hello from channel!")
+ message.extras = [
+ "push": [
+ "notification": [
+ "title": "Channel Push",
+ "body": "Sent push to \(channelName)"
+ ],
+ "data": [
+ "foo": "bar",
+ "baz": "qux"
+ ]
+ ]
+ ] as any ARTJsonCompatible
+
+ realtime.channels.get(channelName).publish([message]) { error in
+ if let error {
+ print("Error sending push to \(channelName) with error: \(error.localizedDescription)")
+ } else {
+ print("Sent push to \(channelName)")
+ }
+ }
+}
+```
+
+
+Now add buttons for these methods in the new `SendPushSection` view struct:
+
+
+```swift
+// MARK: - Send Push Section
+
+struct SendPushSection: View {
+ let appDelegate: AppDelegate
+ @Binding var statusMessage: String
+ @Binding var selectedChannel: String
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 12) {
+ Text("Send Push Notifications")
+ .font(.headline)
+
+ VStack(spacing: 10) {
+ Button(action: {
+ appDelegate.sendPushToDevice()
+ statusMessage = "Sending push to device ID..."
+ }) {
+ HStack {
+ Image(systemName: "phone.badge.checkmark")
+ Text("Send Push to Device ID")
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.purple)
+ .foregroundStyle(.white)
+ .cornerRadius(8)
+ }
+
+ Button(action: {
+ appDelegate.sendPushToClientId()
+ statusMessage = "Sending push to client ID..."
+ }) {
+ HStack {
+ Image(systemName: "person.crop.circle.badge.checkmark")
+ Text("Send Push to Client ID")
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.orange)
+ .foregroundStyle(.white)
+ .cornerRadius(8)
+ }
+
+ HStack(spacing: 8) {
+ Menu {
+ Button(titleForChannel("exampleChannel1")) {
+ selectedChannel = "exampleChannel1"
+ }
+ Button(titleForChannel("exampleChannel2")) {
+ selectedChannel = "exampleChannel2"
+ }
+ } label: {
+ HStack {
+ Image(systemName: "line.3.horizontal.decrease.circle")
+ Text(titleForChannel(selectedChannel))
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.gray.opacity(0.2))
+ .cornerRadius(8)
+ }
+
+ Button(action: {
+ appDelegate.sendPushToChannel(selectedChannel)
+ statusMessage = "Sending push to channel: \(selectedChannel)..."
+ }) {
+ HStack {
+ Image(systemName: "checkmark.circle.fill")
+ Text("Send")
+ }
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.cyan)
+ .foregroundStyle(.white)
+ .cornerRadius(8)
+ }
+ }
+ }
+ }
+ .padding()
+ .background(Color.white)
+ .cornerRadius(12)
+ .shadow(radius: 2)
+ }
+}
+```
+
+
+Update your `ContentView` body to include this section (add it just after the `ChannelSection`):
+
+
+```swift
+// Send Push Section
+SendPushSection(appDelegate: appDelegate, statusMessage: $statusMessage, selectedChannel: $selectedChannel)
+```
+
+
+Build and run your app again. Use the added section to send push notifications.
+
+## 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 (e.g., `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)
+ }
+}
+```
+
+
+### Receiving 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.
+
+Check our GitHub repository for the complete push example: [AblyPushExample](https://github.com/ably/ably-cocoa/tree/main/Examples/AblyPush)
+
+## Next steps
+
+Continue to explore the documentation with Swift as the selected language:
+
+* Check our [push example](https://github.com/ably/ably-cocoa/tree/main/Examples/AblyPush)
+* Understand [token authentication](/docs/auth/token) before going to production.
+* Explore [push notification administration](/docs/push/admin) for managing devices and subscriptions.
+* Learn about [channel rules](/docs/push/channel-rules) for channel-based push notifications.
+* Read more about the [Push Admin API](/docs/api/realtime-sdk?lang=swift#push-admin).
+* Check out the [Push Notifications](/docs/push) documentation for advanced use cases.
+
+You can also explore the [Ably SDK for Swift](https://github.com/ably/ably-cocoa) on GitHub, or visit the [API references](/docs/api/realtime-sdk?lang=swift) for additional functionality.