-
Notifications
You must be signed in to change notification settings - Fork 46
fix(push-guides): restructure APNs, FCM, and web push guides to match react-native/flutter layout #3372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix(push-guides): restructure APNs, FCM, and web push guides to match react-native/flutter layout #3372
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ meta_keywords: "Push Notifications Swift, Ably Push, APNs, iOS Push, Swift push | |
|
|
||
| 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. | ||
| 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. | ||
|
|
||
| <Aside data-type='note'> | ||
| Using an AI coding assistant? [Teach it Ably](/docs/platform/ai-llms#agent-skills) with Agent Skills for all popular AI coding agents. Run `claude plugin add ably/agent-skills` or `npx skills add ably/agent-skills` to get started. | ||
|
|
@@ -16,16 +16,16 @@ Using an AI coding assistant? [Teach it Ably](/docs/platform/ai-llms#agent-skill | |
|
|
||
| 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. For channel-based push, add a rule for the channel with **Push notifications enabled** checked. In the dashboard left sidebar: **Configuration** → **Rules** → **Add** or **Edit** a rule, | ||
| then enable the Push notifications option. See [rules](/docs/channels#rules) for details. | ||
| 5. Install [Xcode](https://developer.apple.com/xcode/). | ||
| 6. You'll need a real iOS device to test push notifications (the simulator doesn't support APNs). | ||
| 7. Set up Apple Push Notification service (APNs) certificates through the [Apple Developer Portal](https://developer.apple.com/). | ||
| * 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 <a id="install-cli"/> | ||
|
|
||
| Use the [Ably CLI](https://github.com/ably/cli) as an additional client to quickly test Pub/Sub features and push notifications. | ||
| Use the [Ably CLI](/docs/platform/tools/cli) as an additional client to quickly test Pub/Sub features and push notifications. | ||
|
|
||
| 1. Install the Ably CLI: | ||
|
|
||
|
|
@@ -205,7 +205,7 @@ func didDeactivateAblyPush(_ error: ARTErrorInfo?) { | |
|
|
||
| Now you are ready to receive push notifications. | ||
|
|
||
| ## Step 3: Receive push notifications <a id="step-3"/> | ||
| ### Handle push notifications <a id="step-2-handle"/> | ||
|
|
||
| Use `UNUserNotificationCenterDelegate` methods to receive push notifications. | ||
| You've set the notification center delegate in the `application:didFinishLaunchingWithOptions` method. | ||
|
|
@@ -239,8 +239,9 @@ func userNotificationCenter(_ center: UNUserNotificationCenter, | |
| ``` | ||
| </Code> | ||
|
|
||
| Push notifications can be sent either directly to your `deviceId` (or `clientId`), | ||
| or posted to a channel, in which case you first need to subscribe your device to that channel: | ||
| ## Step 3: Subscribe to channel push notifications <a id="step-3"/> | ||
|
|
||
| To subscribe your device to a channel so it can receive channel-based push notifications, add the following methods to your `AppDelegate` class: | ||
|
|
||
| <Code> | ||
| ```swift | ||
|
|
@@ -274,23 +275,6 @@ func unsubscribeFromChannel(_ channelName: String) { | |
| ``` | ||
| </Code> | ||
|
|
||
| Sending push notifications using `deviceId` or `clientId` 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](/docs/platform/tools/cli). | ||
|
|
||
| To publish to your client ID using the [Ably CLI](/docs/platform/tools/cli) paste the following command into your terminal: | ||
|
|
||
| <Code> | ||
| ```shell | ||
| ably push publish --client-id push-tutorial-client \ | ||
| --title "Test push" \ | ||
| --body "Hello from CLI!" \ | ||
| --data '{"foo":"bar","baz":"qux"}' | ||
| ``` | ||
| </Code> | ||
|
|
||
| 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 <a id="step-4"/> | ||
|
|
||
| First, in your `PushTutorialApp.swift`, add `@UIApplicationDelegateAdaptor` wrapped `appDelegate` property | ||
|
|
@@ -324,7 +308,7 @@ struct ContentView: View { | |
| let appDelegate: AppDelegate | ||
|
|
||
| @State private var statusMessage = "Ready to start" | ||
| @State private var selectedChannel = "exampleChannel1" | ||
| @State private var selectedChannel = "my-first-push-channel" | ||
|
|
||
| var body: some View { | ||
| NavigationStack { | ||
|
|
@@ -468,8 +452,8 @@ struct SetupPushSection: View { | |
| // Helper to get a user-friendly title for a channel | ||
| func titleForChannel(_ name: String) -> String { | ||
| let titles = [ | ||
| "exampleChannel1": "Channel 1", | ||
| "exampleChannel2": "Channel 2" | ||
| "my-first-push-channel": "Channel 1", | ||
| "my-first-push-channel-2": "Channel 2" | ||
| ] | ||
| return titles[name] ?? name | ||
| } | ||
|
|
@@ -487,11 +471,11 @@ struct ChannelSection: View { | |
| VStack(spacing: 10) { | ||
| HStack(spacing: 8) { | ||
| Menu { | ||
| Button(titleForChannel("exampleChannel1")) { | ||
| selectedChannel = "exampleChannel1" | ||
| Button(titleForChannel("my-first-push-channel")) { | ||
| selectedChannel = "my-first-push-channel" | ||
| } | ||
| Button(titleForChannel("exampleChannel2")) { | ||
| selectedChannel = "exampleChannel2" | ||
| Button(titleForChannel("my-first-push-channel-2")) { | ||
| selectedChannel = "my-first-push-channel-2" | ||
| } | ||
| } label: { | ||
| HStack { | ||
|
|
@@ -546,217 +530,43 @@ struct ChannelSection: View { | |
| </Code> | ||
|
|
||
| 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): | ||
|
|
||
|  | ||
| push notifications and subscribe to channels. | ||
|
|
||
| ### Send push via channel <a id="step-4-send-channel"/> | ||
|
|
||
| To test pushes via channel, subscribe to "Channel 1" in the UI and publish a push notification to the channel using the [Ably CLI](/docs/platform/tools/cli): | ||
|
|
||
| <Code> | ||
| ```shell | ||
| ably push publish --channel exampleChannel1 \ | ||
| --title "Hello" \ | ||
| --body "World!" \ | ||
| --message '{"name":"greeting","data":"Hello World!"}' | ||
| ``` | ||
| </Code> | ||
|
|
||
| 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 push with code <a id="step-5"/> | ||
|
|
||
| Just as you can send push notifications through the Ably CLI or dashboard, you can also send them directly from your app | ||
| using `deviceId` (or `clientId`), 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: | ||
|
|
||
| <Code> | ||
| ```swift | ||
| // MARK: - Send Push Notifications | ||
| ## Step 5: Publish a push notification <a id="step-5"/> | ||
|
|
||
| /// 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")") | ||
| } | ||
| } | ||
| Tap **Activate Push** and wait until the status message displays the received device token. You can get your device ID from the **Get Device Details** button (don't confuse it with the device token). | ||
|
|
||
| /// Send push notification to a specific client ID | ||
| func sendPushToClient() { | ||
| 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")") | ||
| } | ||
| } | ||
| ``` | ||
| </Code> | ||
| ### Publish directly to your device <a id="step-5-direct"/> | ||
|
|
||
| Sending to a channel is just publishing a message on a channel with a `push` `extras` field: | ||
| Publish a push notification directly to your client ID (or device ID using `--device-id` instead of `--client-id`) via the [Ably CLI](/docs/platform/tools/cli): | ||
|
|
||
| <Code> | ||
| ```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)") | ||
| } | ||
| } | ||
| } | ||
| <Code fixed="true"> | ||
| ```shell | ||
| ably push publish --client-id push-tutorial-client \ | ||
| --title "Test push" \ | ||
| --body "Hello from CLI!" \ | ||
| --data '{"foo":"bar","baz":"qux"}' | ||
| ``` | ||
| </Code> | ||
|
|
||
| Now add buttons for these methods in the new `SendPushSection` view struct: | ||
|
|
||
| <Code> | ||
| ```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.sendPushToClient() | ||
| 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) | ||
| } | ||
| ### Publish via a channel <a id="step-5-channel"/> | ||
|
|
||
| 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) | ||
| } | ||
| Subscribe to "Channel 1" in the UI, then publish a push notification to the channel using the [Ably CLI](/docs/platform/tools/cli): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bit odd to have the different labels here. We call it
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a short alias for the button, which has a neighboring button on the other half of the screen, so to save some width, I've made aliases. A subject for clean up too once there only one channel. |
||
|
|
||
| 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) | ||
| } | ||
| } | ||
| <Code fixed="true"> | ||
| ```shell | ||
| ably push publish --channel my-first-push-channel \ | ||
| --title "Hello" \ | ||
| --body "World!" \ | ||
| --message '{"name":"greeting","data":"Hello World!"}' | ||
| ``` | ||
| </Code> | ||
|
|
||
| Update your `ContentView` body to include this section (add it just after the `ChannelSection`): | ||
|  | ||
|
|
||
| <Code> | ||
| ```swift | ||
| // Send Push Section | ||
| SendPushSection(appDelegate: appDelegate, statusMessage: $statusMessage, selectedChannel: $selectedChannel) | ||
| ``` | ||
| </Code> | ||
| 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. | ||
|
|
||
| Build and run your app again. Use the added section to send push notifications. | ||
| 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](/docs/cli/push). To publish push notifications from your own server code instead of the CLI, see [Push notification publishing](https://ably.com/docs/push/publish). | ||
|
|
||
| ## Step 6: Location pushes <a id="step-6"/> | ||
|
|
||
|
|
@@ -769,7 +579,7 @@ Add `Location (when in use)`, `Location (Always)`, `Location Push Service Extens | |
| 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`). | ||
| 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: | ||
|
|
||
|
|
@@ -861,7 +671,7 @@ func didUpdateAblyPush(_ error: ARTErrorInfo?) { | |
| ``` | ||
| </Code> | ||
|
|
||
| ### Receiving location pushes <a id="step-5-receiving"/> | ||
| ### Receive location pushes <a id="step-6-receiving"/> | ||
|
|
||
| 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. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have 2 channels for APNS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the first guide I did, so the choices were a bit chaotic. You are right, this needs to be cleaned up.