Push Notifications Tutorial: Getting Started

In this push notifications tutorial, you’ll learn how to send and handle push notifications with actions.

Version

  • Swift 4.2, iOS 12, Xcode 10
Update Note: Keegan Rush updated this tutorial for Xcode 10 and Swift 4.2. Jack Wu wrote the original tutorial.

iOS developers love to imagine users using their awesome app constantly. But, of course, users will sometimes have to close the app and perform other activities. Laundry doesn’t fold itself, you know! Happily, push notifications allow developers to reach users and perform small tasks — even when users aren’t actively using an app! In this tutorial, you’ll learn how to configure your app to receive push notifications and to display them to your users.

Getting Started

Push notifications have become more powerful since they were first introduced. With iOS 12, push notifications can:

  • Display a short text message.
  • Play a notification sound.
  • Set a badge number on the app’s icon.
  • Provide actions the user can take without opening the app.
  • Show a media attachment.
  • Be silent, allowing the app to perform a task in the background.
  • Group notifications into threads.
  • Edit or remove delivered notifications.
  • Run code to change your notification before displaying it.
  • Display a custom, interactive UI for your notification.

This tutorial covers many of these. To do this tutorial, you’ll need the following to test push notifications:

  • An iOS device: Push notifications don’t work in a simulator, so you’ll need an actual device.
  • An Apple Developer Program membership: To send push notifications, you need a push notification certificate for your App ID, which requires a program membership.
  • PushNotifications: You’ll use this utility app to send notifications to a test device. To install it, follow the instructions under How to install.

To send and receive push notifications, you must perform three main tasks:

  1. Configure your app and register it with the Apple Push Notification service (APNs).
  2. Send a push notification from a server to specific devices via APNs.
  3. Use callbacks in the app to receive and handle push notifications.

Sending push notifications is a responsibility of your app’s server component. Many apps use third parties to send push notifications, while others use custom solutions or popular libraries (e.g., Houston). You’ll only touch on sending push messages in this tutorial, mostly for the purposes of testing the app.

To get started, download the WenderCast starter project using the Download Materials button at the top or bottom of this tutorial. WenderCast is everyone’s go-to source for raywenderlich.com podcasts and breaking news.

In the WenderCast-Starter folder, open WenderCast.xcodeproj with Xcode. Select WenderCast in the Project navigator, then select the WenderCast target. In the General tab, find the Signing section and select your development Team. Build and run on your device:

WenderCast displays a list of podcasts and lets users play them on raywenderlich.com. The problems are that it doesn’t let users know when a new podcast is available and the News tab is empty! You’ll soon fix these issues with the power of push notifications.

Sending and Receiving Push Notifications

Configuring the App

Security is very important for push notifications, since you don’t want anyone else to send push notifications to your users through your app. You’ll need to perform several tasks to configure your app to safely receive push notifications.

Enabling the Push Notification Service

First, change the App ID. In Xcode, highlight WenderCast in the Project navigator, then select the WenderCast target. Select General, then change Bundle Identifier to something unique so Apple’s push notification server can direct pushes to this app.

Change Bundle ID

Next, you need to create an App ID in your developer account and enable the push notification entitlement. Xcode has a simple way to do this: With the WenderCast target still selected, click the Capabilities tab and set the Push Notifications switch On.

After loading, it should look like this:

Push Notification Capability

Note: If any issues occur, visit the Apple Developer Center. You may need to agree to a new developer license, which Apple loves to update, and try again.

Behind the scenes, this creates the App ID and then adds the push notifications entitlement to it. You can log into the Apple Developer Center to verify this:

Verify the Entitlement

That’s all you need to configure for now.

Asking for User Notifications Permission

There are two steps you take to register for push notifications. First, you must get the user’s permission to show notifications. Then, you can register the device to receive remote (push) notifications. If all goes well, the system will then provide you with a device token, which you can think of as an “address” to this device.

In WenderCast, you’ll register for push notifications immediately after the app launches. Ask for user permissions, first.

Open AppDelegate.swift and add the following to the top of the file:

import UserNotifications

Then, add the following method to the end of AppDelegate:

func registerForPushNotifications() {
  UNUserNotificationCenter.current() // 1
    .requestAuthorization(options: [.alert, .sound, .badge]) { // 2
      granted, error in
      print("Permission granted: \(granted)") // 3
  }
}

Going over the code above:

  1. UNUserNotificationCenter handles all notification-related activities in the app.
  2. You invoke requestAuthorization(options:completionHandler:) to (you guessed it) request authorization to show notifications. The passed options indicate the types of notifications you want your app to use – here you’re requesting alert, sound and badge.
  3. The completion handler receives a Bool which indicates if authorization was successful. In this case, you just print the result.
Note: The options you pass to requestAuthorization(options:completionHandler:) can include any combination of UNAuthorizationOptions:
  • .badge: Display a number on the corner of the app’s icon.
  • .sound: Play a sound.
  • .alert: Display text.
  • .carPlay: Display notifications in CarPlay.
  • .provisional: Post non-interrupting notifications. The user won’t get a request for permission if you only use this option, but your notifications will only show silently in the Notification Center.
  • .providesAppNotificationSettings: Indicate that the app has its own UI for notification settings.
  • .criticalAlert: Ignore the mute switch and Do Not Disturb. You’ll need a special entitlement from Apple to use this option as it’s only meant to be used when absolutely necessary.

Add the following near the end of application(_:didFinishLaunchingWithOptions:), just before the return:

registerForPushNotifications()

Calling registerForPushNotifications() here ensures the app will attempt to register for push notifications any time it’s launched.

Build and run. When the app launches, you should receive a prompt that asks for permission to send you notifications.

Prompt for Notifications

Tap Allow and poof! The app can now display notifications. Great! But what if the user declines the permissions? Add this method inside AppDelegate:

func getNotificationSettings() {
  UNUserNotificationCenter.current().getNotificationSettings { settings in
    print("Notification settings: \(settings)")
  }
}

First, you specified the settings you want. This method returns the settings the user has granted. For now you’re printing them, but you’ll circle back here shortly.

In registerForPushNotifications, replace the call to requestAuthorization(options:completionHandler:) with the following:

UNUserNotificationCenter.current()
  .requestAuthorization(options: [.alert, .sound, .badge]) {
    [weak self] granted, error in
      
    print("Permission granted: \(granted)")
    guard granted else { return }
    self?.getNotificationSettings()
}

You’ve added a call to getNotificationSettings in the completion handler. This is important as the user can, at any time, go into the Settings app and change their notification permissions. The guard avoids making this call in cases where permission wasn’t granted.

Registering With APNs

Now that you have permissions, you’ll now actually register for remote notifications!

In getNotificationSettings(), add the following beneath the print inside the closure:

guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
  UIApplication.shared.registerForRemoteNotifications()
}

Here, you verify the authorizationStatus is .authorized: The user has granted notification permissions. If so, you call UIApplication.shared.registerForRemoteNotifications() to kick off registration with the Apple Push Notification service. You need to call it on the main thread, or you’ll receive a runtime warning.

Add the following two methods to the end of AppDelegate. iOS will call these to inform you about the result of registerForRemoteNotifications():

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

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

Once the registration completes, iOS calls application(_:didRegisterForRemoteNotificationsWithDeviceToken:) if successful. If not, it calls application(_:didFailToRegisterForRemoteNotificationsWithError:).

The current implementation of application(_:didRegisterForRemoteNotificationsWithDeviceToken:) looks cryptic, but it’s simply taking deviceToken and converting it to a string. The device token is the fruit of this process. It’s provided by APNs and uniquely identifies this app on this particular device. When sending a push notification, the server uses tokens as “addresses” to deliver to the correct devices. In your app, you would now send this token to your server, so that it could be saved and later used for sending notifications.

That’s it! Build and run on a device. You should receive a token in the console output. Here’s an example:

Copy this token, you’ll need it for testing.

Note: There are several reasons why registration might fail. Most of the time it’s because the app is running on a simulator, or because the App ID was not properly configured. The error message generally provides a good hint for what’s wrong.

Creating an Authentication Key

You have a bit more configuration to do before you can send a push notification. Head over to the Apple Developer Member Center and log in.

Sending push notifications has gotten easier since Apple’s introduction of Authentication Keys. Previously, you would have to follow a complicated process to create a push notification certificate for each app that needs to send notifications. Now, you only need to create one key, which can be used by any of your apps.

In the member center, select Certificates, Identifiers & Profiles, then find Keys ▸ All on the left hand pane. In the upper-right corner, click on the + button to create a new key.

Give the key a name, such as Push Notification Key. Under Key Services, select Apple Push Notifications service (APNs).

Create New Key

Click Continue and then Confirm on the next screen to create your new key. Tap Download. The downloaded file will be named something like AuthKey_4SVKWF966R.p8. Keep track of this file — you’ll need it to send your notifications! The 4SVKWF966R part of the file name is the Key ID. You’ll also need this.

The final piece that you need is your Team ID. Navigate to the Membership Details page of the member center to find it.

Whew! That was a lot to get through, but it was all worth it. With your new key, you are now ready to send your first push notification!

Sending a Push Notification

If you haven’t downloaded the PushNotifications utility yet, now’s the time.

Launch PushNotifications and complete the following steps:

  1. Under Authentication, select Token.
  2. Click the Select P8 button and select the .p8 file from the previous section.
  3. Enter your Key ID and Team ID in the relevant fields.
  4. Under Body, enter your app’s Bundle ID and your device token.
  5. Change the request body to look like this:
  6. {
      "aps": {
        "alert": "Breaking News!",
        "sound": "default",
        "link_url": "https://raywenderlich.com"
      }
    }
    
  7. On your test device background the app or lock the device.
  8. Click the Send button in PushNotifications.

PushNotifications Utility

You should receive your first push notification:

Your First Push Notification

Note: You won’t see anything if the app is open and running in the foreground. The notification is delivered, but there’s nothing in the app to handle it yet. First, close the app and send the notification again.

Common Issues

There are a couple problems you might encounter:

  • Some notifications are received but not all: If you’re sending many push notifications simultaneously and you only receive a few, fear not! That is intended behavior. APNs maintains a QoS (Quality of Service) queue for each device. The size of this queue is 1, so if you send multiple notifications, the last notification is overridden.
  • Problem connecting to Push Notification Service: One possibility could be that there is a firewall blocking the ports used by APNs. Make sure you unblock these ports.

Anatomy of a Basic Push Notification

Before you move on to handling push notifications, take a look at the body of the notification you’ve sent:

{
  "aps": {
    "alert": "Breaking News!",
    "sound": "default",
    "link_url": "https://raywenderlich.com"
  }
}

The payload is a JSON dictionary that contains at least one item, aps, which itself is also a dictionary. In this example, aps contains the fields alert, sound, and link_url. When the device receives this push notification, it shows an alert view with the text “Breaking News!” and plays the standard sound effect.

link_url is actually a custom field. You can add custom fields to the payload like this and they will get delivered to your application. Since you aren’t handling it inside the app yet, this key/value pair currently does nothing.

There are seven built-in keys you can add to the aps dictionary:

  • alert: This can be a string, like in the previous example, or a dictionary itself. As a dictionary, it can localize the text or change other aspects of the notification.
  • badge: This is a number that will display in the corner of the app icon. You can remove the badge by setting this to 0.
  • sound: Name of a custom notification sound’s file located in the app. Custom notification sounds must be shorter than 30 seconds and have a few restrictions.
  • thread-id: Use this key to group notifications.
  • category: This defines the category of the notification, which is is used to show custom actions on the notification. You will explore this shortly.
  • content-available: By setting this key to 1, the push notification becomes silent. You will learn about this in the Silent Push Notifications section.
  • mutable-content: By setting this key to 1, your app can modify the notification before displaying it.

Outside of these, you can add as much custom data as you want, as long as the payload does not exceed 4,096 bytes.

Once you’ve had enough fun sending push notifications to your device, move on to the next section!

Handling Push Notifications

In this section, you’ll learn how to perform actions when your app receives notifications and when users tap on them.

What Happens When You Receive a Push Notification?

When your app receives a push notification, iOS calls a method in UIApplicationDelegate.

You’ll need to handle a notification differently depending on what state your app is in when it’s received:

  • If your app wasn’t running and the user launches it by tapping the push notification, the push notification is passed to your app in the launchOptions of application(_:didFinishLaunchingWithOptions:).
  • If your app was running either in the foreground or the background, the system notifies your app by calling application(_:didReceiveRemoteNotification:fetchCompletionHandler:). If the user opens the app by tapping the push notification, iOS may call this method again, so you can update the UI and display relevant information.

In the first case, WenderCast will create the news item and open up directly to the News tab. Add the following code to the end of application(_:didFinishLaunchingWithOptions:), just before the return statement:

// Check if launched from notification
let notificationOption = launchOptions?[.remoteNotification]

// 1
if let notification = notificationOption as? [String: AnyObject],
  let aps = notification["aps"] as? [String: AnyObject] {
  
  // 2
  NewsItem.makeNewsItem(aps)
  
  // 3
  (window?.rootViewController as? UITabBarController)?.selectedIndex = 1
}

This code does three things:

  1. It checks whether the value for UIApplication.LaunchOptionsKey.remoteNotification exists in launchOptions. If it does, then your app was launched from a notification. This will be the push notification payload you sent.
  2. If it exists, it grabs the aps dictionary and creates a NewsItem with its contents.
  3. It changes the selected tab of the tab controller to the News section.

To test this, you need to edit the scheme of WenderCast:

Edit the Scheme

Click on the WenderCast scheme and select Edit Scheme…. Select Run from the sidebar, then in the Info tab select Wait for executable to be launched:

Select 'Wait for executable to be launched'

This option will make the debugger wait for the app to be launched for the first time after installing to attach to it.

Build and run. Once it’s done installing, send out more breaking news. Tap on the notification, and the app should open up to news:

Breaking News

Note: If you stop receiving push notifications, it is likely that your device token has changed. This can happen if you uninstall and reinstall the app. Double check the device token to make sure.

To handle the other case for receiving push notifications, add the following method to AppDelegate:

func application(
  _ application: UIApplication,
  didReceiveRemoteNotification userInfo: [AnyHashable: Any],
  fetchCompletionHandler completionHandler:
  @escaping (UIBackgroundFetchResult) -> Void
) {
  guard let aps = userInfo["aps"] as? [String: AnyObject] else {
    completionHandler(.failed)
    return
  }
  NewsItem.makeNewsItem(aps)
}

This method creates a new NewsItem from the contents of a push message.

Remember that this method is called when the app is running. Change the scheme back to launching the app automatically. In the Scheme editor, under Launch select Automatically.

Build and run. Keep the app running in the foreground and on the News tab. Send another news push notification and watch as it appears in the feed:

That’s it! Your app can now magically receive breaking news as it happens.

Something important consider: Many times, push notifications may be missed. This is OK for WenderCast, since having the full list of news isn’t too important for this app. Generally, you should not use push notifications as the only way of delivering content.

Instead, push notifications should signal that there is new content available and let the app download the content from the source (e.g., from a REST API). WenderCast is a bit limited in this sense, as it doesn’t have a true server-side component.

Actionable Notifications

Actionable notifications let you add custom buttons to the notification itself. You may have noticed this on email notifications or Tweets that let you “reply” or “favorite” on the spot.

Your app can define actionable notifications when you register for notifications by using categories. Each category of notification can have a few preset custom actions.

Once registered, your server can set the category of a push notification. The corresponding actions will be available to the user when received.

For WenderCast, you will define a News category with a custom action named View. This action will allow users to view the news article in the app if they choose to.

In registerForPushNotifications(), insert the following just below the guard and above the call to getNotificationSettings():

// 1
let viewAction = UNNotificationAction(
  identifier: Identifiers.viewAction, title: "View",
  options: [.foreground])

// 2
let newsCategory = UNNotificationCategory(
  identifier: Identifiers.newsCategory, actions: [viewAction],
  intentIdentifiers: [], options: [])

// 3
UNUserNotificationCenter.current().setNotificationCategories([newsCategory])

Here’s what the new code does:

  1. Create a new notification action, with the title View on the button, that opens the app in the foreground when triggered. The action has a distinct identifier, which is used to differentiate between other actions on the same notification.
  2. Next, define the news category, which will contain the view action. It also has a distinct identifier that your payload will need to contain, in order to specify that the push notification belongs to this category.
  3. Finally, by calling setNotificationCategories, register the new actionable notification.

That’s it! Build and run the app to register the new notification settings.

Background the app and then send the following payload via the PushNotifications utility:

{
  "aps": {
    "alert": "Breaking News!",
    "sound": "default",
    "link_url": "https://raywenderlich.com",
    "category": "NEWS_CATEGORY"
  }
}

If all goes well, you should be able to pull down on the notification to reveal the View action:

Actionable Notification

Nice! Tapping on it will launch WenderCast, but it won’t do anything. To get it to display the news item, you need to do some more event handling in the delegate.

Handling Notification Actions

Whenever a notification action is triggered, UNUserNotificationCenter informs its delegate. Back in AppDelegate.swift, add the following class extension to the bottom of the file:

extension AppDelegate: UNUserNotificationCenterDelegate {
  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void) {
    
    // 1
    let userInfo = response.notification.request.content.userInfo
    
    // 2
    if let aps = userInfo["aps"] as? [String: AnyObject],
      let newsItem = NewsItem.makeNewsItem(aps) {
      
      (window?.rootViewController as? UITabBarController)?.selectedIndex = 1
      
      // 3
      if response.actionIdentifier == Identifiers.viewAction,
        let url = URL(string: newsItem.link) {
        let safari = SFSafariViewController(url: url)
        window?.rootViewController?.present(safari, animated: true,
                                            completion: nil)
      }
    }
    
    // 4
    completionHandler()
  }
}

This is the callback you get when the app is opened by a custom action. It might look like there’s a lot going on, but there’s not much new, here:

  1. Get the aps dictionary.
  2. Create a NewsItem from the dictionary and navigate to the News tab.
  3. Check the action identifier, which is passed in as identifier. If it is the “View” action and the link is a valid URL, it displays the link in an SFSafariViewController.
  4. Call the completion handler the system passes to you.

There is one last bit: You have to set the delegate on UNUserNotificationCenter. Add this line to the top of application(_:didFinishLaunchingWithOptions:):

UNUserNotificationCenter.current().delegate = self

Build and run. Close the app again, then send another news notification with the following payload:

{
  "aps": {
    "alert": "New Posts!",
    "sound": "default",
    "link_url": "https://raywenderlich.com",
    "category": "NEWS_CATEGORY"
  }
}

Pull down the notification and tap on the View action, and you should see WenderCast present a Safari View controller right after it launches:

Congratulations, you’ve implemented an actionable notification! Send a few more and try opening the notification in different ways to see how it behaves.

Silent Push Notifications

Silent push notifications can wake your app up silently to perform some tasks in the background. WenderCast can use this feature to quietly refresh the podcast list.

With a proper server component this can be very efficient. Your app won’t need to constantly poll for data. You can send it a silent push notification whenever new data is available.

To get started, select the WenderCast target again. Now tap the Capabilities tab and set the Background Modes switch On. Then check the Remote notifications option:

Background Modes

Now, your app will wake up in the background when it receives one of these push notifications.

Inside AppDelegate, find application(_:didReceiveRemoteNotification:). Replace the call to NewsItem.makeNewsItem() with the following:

// 1
if aps["content-available"] as? Int == 1 {
  let podcastStore = PodcastStore.sharedStore
  // 2
  podcastStore.refreshItems { didLoadNewItems in
    // 3
    completionHandler(didLoadNewItems ? .newData : .noData)
  }
} else  {
  // 4
  NewsItem.makeNewsItem(aps)
  completionHandler(.newData)
}

Going over the code:

  1. You check to see if content-available is set to 1, to see if it is a silent notification.
  2. You refresh the podcast list, which is an asynchronous network call.
  3. When the refresh is complete, call the completion handler to let the system know whether any new data was loaded.
  4. If it isn’t a silent notification, assume it’s news and create a news item.

Be sure to call the completion handler with the honest result. The system measures the battery consumption and time that your app uses in the background and may throttle your app if needed.

That’s all there is to it; to test it, build and run, foreground the app and push the following payload via PushNotifications:

{
  "aps": {
    "content-available": 1
  }
}

If all goes well, nothing should happen, unless a new podcast was just added to the remote database. To confirm the code ran as expected, set a breakpoint in application(_:didReceiveRemoteNotification:fetchCompletionHandler:) and step through it after a push.

Where to Go From Here?

Congratulations! You’ve completed this tutorial and made WenderCast a fully featured app with push notifications!

You can download the completed project using the Download Materials button at the top or bottom of this tutorial.

Want to dive deeper into everything you can do with push notifications, like building custom UIs and sending critical alerts? Our Push Notifications by Tutorials book will teach you the advanced features of push notifications.

Even though push notifications are an important part of apps nowadays, it’s also very common for users to decline permissions to your app if it sends notifications too often. But with thoughtful design, push notifications can keep your users coming back to your app again and again!

push notifications tutorial

This cat received a push notification that his dinner was ready!

I hope you’ve enjoyed this push notifications tutorial. If you have any questions, feel free to leave them in the discussion forum below.

Contributors

Comments