Your Complete Guide to Push Notifications in SwiftUI

Chase
11 min readMay 26, 2024

--

Push notifications are a feature that many apps use, but there hasn’t been much content around how they work or how to implement them. In this tutorial we will make this complex topic easier to understand and implement in your own project.

A notification with a title of Your Complete Guide to Push Notifications and a body of dont forget to follow me and subscribe

Before we get started, please take a couple of seconds to follow me and 👏 clap for the article so that we can help more people learn about this useful content.

A high level overview of how push notifications work

Setting up push notifications:

  1. Your app requests notifications access from the user.
  2. You request a token from Apple.
  3. Apple Push Notification Service (APNs) sends that device a unique token.
  4. You send that token to your push sever.
A diagram explaining  how to the various services are connected in the push notification flow

Sending a push notification:

  1. Your push server sends a request to APNs to send a notification to a specific device.
  2. APNs sends the notification to that device.
A diagram of the flow for sending a push notification and how the various services are connected

Your push notification server doesn’t directly send notifications to a users device, it sends the notification to Apples push notification service and then Apple will forward the notification to the users device (as shown in the image below). These services are also great places to test the steps in our tutorial and in your own code. So we will walk through how to test notifications locally on your device and how to test from Apples Push Notification Service.

A connection diagram of that shows your push server on the left connected to APNs in the middle which is connected to your app on the right

At a high level, that all sounds fairly simple. In the following sections, we will go into more detail as to what happens in each step (and the steps this high level overview leaves out), along with how to implement that each one in your project.

Prerequisites

Before we get started, there are a few things that must be set up before proceeding any further in the tutorial:

  • You must have a paid Apple developer account for push notifications to work.
  • The paid profile must be the one selected in the Xcode project that you want to add notifications to.

Request notifications access from the user

To request access to send the user notifications, we will need to import UserNotifications and request authorization somewhere in our view. In the example code below, we have added this request as the action that takes place when a button is pressed. Note: If the user does not accept the request, none of the following steps will display any kind of notification.

import SwiftUI
import UserNotifications

struct ContentView: View {
let notificationCenter = UNUserNotificationCenter.current()

var body: some View {
VStack {
Button("Request Notification Access") {
Task {
do {
try await notificationCenter.requestAuthorization(options: [.alert, .badge, .sound])
} catch {
print("Request authorization error")
}
}
}
.buttonStyle(.borderedProminent)
}
}
}

Create a sample notification for testing

Creating a sample notification allows us to quickly and easily test the code that we write as we are going along in the process of creating push notifications. The notification that we have added below follows a similar format to JSON, however, the extension of this file should end with “.apns”. For example, lets call this file sample-push-notification.apns. If you would like to learn more about the options for building your own notification, check out Apples docs here

{
"Simulator Target Bundle": "YOUR_BUNDLE_ID_GOES_HERE",
"aps": {
"alert": {
"title": "This is a test notification title",
"body": "This is a test notification body"
}
}
}

Naming the file in that way will allow us to drag and drop this file onto the simulator and it will display a notification as long as the app is in the background (at least for now). We can also trigger the notification to display using the terminal command that has been added below.

xcrun simctl push booted PATH_TO_YOUR_SAMPLE_PUSH_NOTIFICATION/sample-push-notification.apns

This code will trigger the notification to be display on a simulator that is already running. Again, the notification will not currently be displayed unless the app is running in the background.

In fact, now that we have requested access and built a sample push notification to test with, go ahead and run the app, allow notifications, press the home button to move the app to the background, and drag and drop the sample-push-notification file onto the simulator and you should see our sample notification already being displayed.

A screenshot of our sample notification being displayed in our simulator

Preparing for push notifications

At the time of writing, push notifications require us to use an App Delegate file, yes, even if the rest of our app is built using SwiftUI. Hopefully Apple will update this in the near future, but for now we will create a custom App Delegate to use in our SwiftUI application. You may notice that I decided to name the class CustomAppDelegate, the reason is to make sure we don’t run into any naming collision issues with anything else that Apple may be using. I have added comments to the rest of this code to help document what is going on in the various lines of code and functions.

//  CustomAppDelegate.swift
import SwiftUI
import UserNotifications

class CustomAppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
// This gives us access to the methods from our main app code inside the app delegate
var app: Push_Notifications_ExampleApp?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// This is where we register this device to recieve push notifications from Apple
// All this function does is register the device with APNs, it doesn't set up push notifications by itself
application.registerForRemoteNotifications()

// Setting the notification delegate
UNUserNotificationCenter.current().delegate = self

return true
}

func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Once the device is registered for push notifications Apple will send the token to our app and it will be available here.
// This is also where we will forward the token to our push server
// If you want to see a string version of your token, you can use the following code to print it out
let stringifiedToken = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("stringifiedToken:", stringifiedToken)
}
}

extension CustomAppDelegate: UNUserNotificationCenterDelegate {
// This function lets us do something when the user interacts with a notification
// like log that they clicked it, or navigate to a specific screen
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
print("Got notification title: ", response.notification.request.content.title)
}

// This function allows us to view notifications in the app even with it in the foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
// These options are the options that will be used when displaying a notification with the app in the foreground
// for example, we will be able to display a badge on the app a banner alert will appear and we could play a sound
return [.badge, .banner, .list, .sound]
}
}

In the file that holds our “@main” annotation we will add a property wrapper for the app delegate and add an onAppear modifier which will make sure that our app delegate and app are connected.

//  Push_Notifications_ExampleApp.swift
import SwiftUI

@main
struct Push_Notifications_ExampleApp: App {
// this gives us access to our app delegate in SwiftUI
@UIApplicationDelegateAdaptor private var appDelegate: CustomAppDelegate

var body: some Scene {
WindowGroup {
ContentView()
.onAppear(perform: {
// this makes sure that we are setting the app to the app delegate as soon as the main view appears
appDelegate.app = self
})
}
}
}

The last step we will need to do in order to prepare our app for push notifications is to add the push notification capability. To do this we will go to the signing and capabilities tab, and click the add capability button. Once there, we will choose to add the Push Notifications capability. You will want to make sure that Push Notifications appears in the capabilities list (as seen below in the red square), if it doesn’t appear after a few seconds you may need to try adding it again.

If you are working with a project that automatically manages certificates and profiles that should be all you need to do. However, if you are using something like Fastlane that does not automatically manage certificates and profiles, then at this point you may need to regenerate your certificates and profiles before moving on to the next step. You will be able to tell if you need to regenerate the certs and profiles if after adding the push notification capability you see a warning pop up in the Signing section of this tab.

A screenshot of the signing and capabilities screen of xcode with the add capability button and the push notification capability highlighted

Testing remote push notifications

Earlier in this article we tested push notifications locally, in this section we will cover how to send push notifications directly from Apples Push Notification service (APNs) for testing. To send a test push notification we will go back to Xcode. In our CustomAppDelegate code there is a print statement added to one of the methods. Go ahead and run the code either on a simulator or a physical device (if you haven’t already done so), request notifications authorization, click allow, then copy and paste the value of the stringifiedToken that has been printed out to the logs.

To create a push notification to test with we will go to developer.apple.com and from the Account page, we will click the Push Notification option. This will take us to the Push Notifications Console.

The apple developer account screen with the push notifications option highlighted

Inside the push notification console, we will make sure the app we want to send notifications to is selected (highlighted with the red ellipse in the image below). Once we have our app selected, we will click the Create New Notification button.

This form allows us to send a completely custom notification to a specific device which is perfect for testing. In our example there are only three fields that we need to change. We will give our test notification a name of “Test Notification”, we will paste the stringifiedToken that we copied earlier and paste it into the Device Token field, and we will change the apns-priority to high (which will try to send the notification immediately). If we left the priority at medium or low, the notification may not be delivered in a timely manner since notifications can be delayed if the device is low on battery or high in memory usage. Once we have the notification set up the way we want it, make sure your device is running where you can see it, and in just a few seconds of clicking the send button (in the upper right corner of the push console) you should see the notification appear on your device.

A screenshot of the test push notification we will send to our device

Creating an APNs key to send push notifications from your push server

This key tells Apple that your push server is allowed to send notifications to your users devices. Currently, Apple only allows you to create two APNs keys per account. This is a realistic limit for the number of keys since we typically want all of our apps to share the same APNs key because any push notification service would be talking to our unique Apple account.

To create the key, we will go developer.apple.com and navigate to the Certificates, IDs, and Profiles section of our Account and from there we will choose Keys.

A screenshot of a developer account page with the keys option highlighted

Once on the Keys page, we will click the button to Create a new key. Note: If your account already has keys listed on this page, you may be able to use a key that has already been generated.

A screenshot of an empty keys section in the apple developer portal

When creating a key, we will need to give it a generic name since this key will work for any service and any app tied to your account, and then check the box for Apple Push Notification service (APNs). Once we have completed both requirements, we will click continue. On the next screen we will confirm our name our choice, then we will click Register and move on to the last screen.

A screenshot of us naming our key and choosing the push notification option

On this screen pay close attention to the warning banner. Our newly generated key can only be accessed once. It is very important to store this key in a safe and secure location that is backed up well such as in a password manager or possibly a private git repository. This key gives any push service access to send notifications to our apps and users, which is why it is important to NOT store this key in any public location.

A screenshot of the screen in the apple developer portal where we have just created a push notification key

Once we have our key downloaded we will need to add the credentials which are the Key ID, the Team ID of the Apple account (you have a team ID even if you are an individual developer), and the contents of the p8 file to our push service. Our push service is usually a third party service like Firebase, AirShip, etc, or you may have setup your own push server. Once we have added the credentials to our push server, we are ready to test out push notifications. Since there are any number of push notification services that you may be using, you will need to refer to the docs for the specific vendor that was chosen to find out how to test push notifications with their service.

If you got value from this article, please consider following me, 👏 clapping for this article, or sharing it to help others more easily find it.

If you have any questions on the topic or know of another way to accomplish the same task, feel free to respond to the post or share it with a friend and get their opinion on it. If you want to learn more about native mobile development, you can check out the other articles I have written here: https://medium.com/@jpmtech. If you want to see apps that have been built with native mobile development, you can check out my apps here: https://jpmtech.io/apps. Thank you for taking the time to check out my work!

--

--