All videos. All books. One low price.

Get unlimited access to all video courses and books on this site with the new raywenderlich.com Ultimate Subscription. Plans start at just $19.99/month.

Home iOS & Swift Tutorials

Push Notifications Tutorial: Getting Started

Push notifications allow developers to reach users, 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 or perform other tasks.

5/5 7 Ratings

Version

  • Swift 5, iOS 14, Xcode 12
Update Note: Chuck Krutsinger updated this tutorial for Xcode 11 and Swift 5. Jack Wu wrote the original tutorial, and Keegan Rush wrote an earlier update.

iOS developers love to imagine people 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.
  • Display them to your users or perform other tasks.

Getting Started

What are push notifications? They are messages sent to your app through the Apple Push Notification service (APNs) even if your app isn’t running or the phone is sleeping. What can you use push notifications for?

  • Display a short text message, called an alert, that draws attention to something new in your app.
  • Play a notification sound.
  • Set a badge number on the app’s icon to let the user know there are new items.
  • 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.
  • And probably more.

This tutorial covers many of these uses to help you get started creating push notifications in your apps. To complete this tutorial, you’ll need the following:

  • Xcode 11.4 or later. Earlier versions of Xcode don’t support push notifications using the simulator.
  • An Apple Developer Program membership to be able to compile the app with the Push Notifications entitlement.
Note: Later in the tutorial, you’ll learn how to send push notifications to a real device in Sending to a Real Device.

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

  1. Configure your app and register it with the APNs.
  2. Send a push notification from a server to specific devices via APNs. You’ll simulate that with Xcode.
  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. Others use custom solutions or popular libraries (such as Houston). You’ll only touch on sending push messages in this tutorial, so be sure to check out the Where to Go From Here? section to build on your knowledge of push notifications.

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 starter folder, open WenderCast.xcodeproj. Select WenderCast in the Project navigator, then select the WenderCast target. In the General & Capabilities tab, select your development team. Build and run in a simulator.

Build and run the starter project

WenderCast displays a list of raywenderlich.com podcasts and lets users play them. But 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. 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 securely receive push notifications.

Enabling the Push Notification Service

First, you have to change the bundle identifier. 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 the bundle identifier

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 Signing & Capabilities tab and then click the + Capability button. Type “push” in the filter field and press Enter.

Add the push notifications capability

After adding the push notifications entitlement, your project should look like this:

Project with push notifications entitlement

Note: If any issues occur, visit the Apple Developer Center. You might 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:

App ID configuration showing push notifications entitlement

That’s all you need to configure for now. You are ready to start enhancing the app.

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() {
  //1
  UNUserNotificationCenter.current() 
    //2
    .requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
      //3
      print("Permission granted: \(granted)")
    }
}

What this code does:

  1. UNUserNotificationCenter handles all notification-related activities in the app, including push notifications.
  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 that indicates whether authorization was successful. In this case, you simply 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 a text notification.
  • .carPlay: Display notifications in CarPlay.
  • .provisional: Post non-interrupting notifications. The user won’t get a request for permission if you use only 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, because it’s meant only for very special use cases.

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 to do more with this.

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

UNUserNotificationCenter.current()
  .requestAuthorization(
    options: [.alert, .sound, .badge]) { [weak self] granted, _ 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 because 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 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 this on the main thread, or you’ll receive a runtime warning.

Add the following to the end of AppDelegate:

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)")
}

This method is called by iOS whenever a call to registerForRemoteNotifications() succeeds. The code may look cryptic, but it’s simply taking a received 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 to save and use later on for sending notifications.

Now add the following:

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

This method is called by iOS if registerForRemoteNotifications() fails. You’re just printing the error for now.

That’s it! Build and run. Because you are on a simulator, you’ll see a Failed to register error. You can ignore that for now. Later, when you run on a real device, you should receive a token in the console output. Here’s an example:

Example of a device token

Note: There are several reasons why registration might fail. Most often, it’s because the App ID was not properly configured. The error message generally provides a good hint for what’s wrong.

Sending a Simulated Push Notification

Use a text editor to create a file called first.apn, which you’ll pass to Xcode’s simctl utility. Paste in the following JSON text and save the file.

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

The structure of this JSON will be explained in the next section. Patience, grasshopper.

Build and run the app again on the simulator, then background the app or lock the device. The app is not yet able to process push notifications while in the foreground.

To use simctl, you’ll need to know the device identifier for the simulator that you are running the app in. To get the identifier, in Xcode, select Windows ▸ Devices and Simulators, then select the Simulators tab at the top and select the simulator you’re using from the list on the left. Use your mouse to copy the identifier. You might need to widen the dialog box to see it fully.

Finding the device identifier in Xcode

Open the Terminal app and type in the following command: xcrun simctl push device_identifier bundle_identifier first.apn. Replace device_identifier with the device identifier you copied from Xcode and replace bundle_identifier with the app’s bundle identifier — the one you used when you first set up the project. Here’s an example:

Using xcrun simctl to send a push notification from the terminal

Run the command and you’ll see the push notification appear on the simulator!

Push notification appears on simulator

Tap on the notification to launch the app.

App launched from the push notification

Isn’t that cool? :]

Looking at 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 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. Because you aren’t handling it inside the app yet, this key/value pair currently does nothing.

Correction: An astute reader pointed out that Apple’s documentation states that custom content such as link_url should be at a peer level to the aps dictionary entry rather than inside of it. It still works if you place it inside, but we always suggest adhering to Apple’s documentation and will correct this in our next update to this tutorial.

There are eight built-in keys you can add to the aps dictionary (see the official Payload Key Reference for more information):

  • alert: This can be a string, as in the previous example, or a dictionary. 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. These must be shorter than 30 seconds.
  • thread-id: Use this key to group notifications.
  • category: This defines the category of the notification, which is used to show custom actions on the notification. You’ll explore this shortly.
  • content-available: By setting this key to 1, the push notification becomes silent. You’ll learn about this in the Silent Push Notifications section below.
  • mutable-content: By setting this key to 1, your app can modify the notification before displaying it.
  • target-content-id: This is the identifier of the window brought forward.

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 trying out these options and sending push notifications to your simulator, 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 them.

Understanding 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 the notification is received:

  • If your app wasn’t running and the user launches it by tapping the push notification, iOS passes the notification 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:). When 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 directly to the News tab. In AppDelegate.swift, 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 is what you’re doing:

  1. Check whether the value for UIApplication.LaunchOptionsKey.remoteNotification exists in launchOptions. If it does, then your app was launched from a notification. This will contain the push notification payload you sent.
  2. Since the aps dictionary exists, create a NewsItem with it.
  3. Change the selected tab of the tab controller to the News section.

To test this, you need to edit the scheme of WenderCast. First, build and run to install the latest code on the simulator. Then, click the WenderCast scheme and select Edit Scheme…:

Edit the Scheme

Select Run from the sidebar, then in the Info tab select Wait for executable to be launched:

Choose wait for the 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 using xcrun simctl as before. Tap the notification, and the app will open to news:

App opened to the news tab

To handle the situation where your app is running when a push notification is received, add the following 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 tries to extract the aps from the supplied userInfo object and, if successful, creates a new NewsItem from it.

Since iOS calls this method when the app is running, you need to change the scheme back to launching the app automatically to test it. 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:

Second push notification shown in the news tab

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

Note: Push notifications are not guaranteed to arrive. This is OK for WenderCast, because having the full list of news isn’t too important for this app. In general, though, 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 (for example, from a REST API).

Working With Actionable Notifications

Actionable notifications let you add custom buttons to the notification itself. You might 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’ll 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])

Going through this, step-by-step:

  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 iOS uses to differentiate between other actions on the same notification.
  2. Define the news category, which will contain the view action. This also has a distinct identifier that your payload will need to contain to specify that the push notification belongs to this category.
  3. Register the new actionable notification by calling setNotificationCategories.

Build and run the app to register the new notification settings.

Background the app and then send the following payload via the xcrun simctl utility:

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

When the notification appears, pull down on it to reveal the View action:

Push notification with the view action revealed

Nice! Tapping View will launch WenderCast, but it doesn’t do anything exciting just yet. 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 gets triggered, UNUserNotificationCenter informs its delegate. Back in AppDelegate.swift, add the following class extension to the bottom of the file:

// MARK: - UNUserNotificationCenterDelegate

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 opens because of a custom action. It might look like there’s a lot going on, but there’s not much new here:

  1. Get the userInfo dictionary.
  2. Create a NewsItem from the aps dictionary and navigate to the News tab.
  3. Check the actionIdentifier. 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 the View action, and you’ll see WenderCast present a Safari View controller right after it launches:

Notification link URL opened in a Safari view

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

Sending to a Real Device

If you don’t want to send push notifications to a real device or you don’t need silent push notifications yet, you can skip this section and go to Where to Go From Here?

However, if you want to get a feel for how to send push notifications to a real device and try silent push, then you need to do some additional setup. Download the PushNotifications utility. You’ll use this utility app to send notifications to a real device. To install it, follow the instructions under How to install. Pay special attention to how to open the app because you’ll have to change some settings to run this utility.

Head over to the Apple Developer Member Center and log in.

Sending push notifications requires an Authentication Key. In the member center, select Certificates, Identifiers & Profiles, then find Keys on the left pane. To the right of the Keys title is a + button. Click it to create a new key.

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

Example push notification key

Click Continue and then Register on the next screen to create your new key. Tap Download. The downloaded file will have a name 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.

You did it! With your new key, you are now ready to send your first push notification! You need just one more thing.

Run the app on your real device and copy the device token from the debugger console and have it ready.

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:
{
  "aps": {
    "alert": "Breaking News!",
    "sound": "default",
    "link_url": "https://raywenderlich.com"
  }
}

Click the Send button in PushNotifications.

Push notifications tester

You should receive your push notification:

Your First Push Notification

Troubleshooting Common Issues

Here are a couple problems you might encounter:

  • Some notifications arrive, but not all: If you’re sending many push notifications simultaneously but you receive only a few, fear not! That is by design. 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.

Using 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 click the Signing & Capabilities tab and add the Background Modes capability. Then check the Remote notifications option:

Check the remote notifications option for background modes entitlement

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

In AppDelegate.swift, find application(_:didReceiveRemoteNotification:fetchCompletionHandler:). 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. If so, this 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 the app loaded any new data.
  4. If it isn’t a silent notification, then it is a news item, so make 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 the PushNotifications utility:

{
  "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 the notification is sent.

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, such as building custom UIs and sending critical alerts? Our Push Notifications by Tutorials book will teach you the advanced features of push notifications.

Another resource is the Push Notifications Tutorial for iOS: Rich Push Notifications tutorial.

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

Cute cat notification bell

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.

Average Rating

5/5

Add a rating for this content

7 ratings

More like this

Contributors

Comments