UIKit Apprentice, Second Edition – Now Updated!

Learn iOS and Swift from scratch. Build four powerful apps—with support for iPad and Dark Mode. Publish apps to the App Store.

Home iOS & Swift Tutorials

Firebase Cloud Messaging for iOS: Push Notifications

Learn how to use Firebase Cloud Messaging to send and receive remote push notifications in your SwiftUI iOS app.

4.3/5 6 Ratings

Version

  • Swift 5, iOS 14, Xcode 12

The ways people interact with apps vary from person to person. This can make it difficult to know when your customers will be using your app, especially if you need to give them important information. Push notifications provide a unified, consistent way to inform your app’s users of just about anything.

To receive a push notification, your device needs to be registered with the Apple Push Notification service (APNs) by receiving a unique device token. Once the device is registered, you can send push notifications to the device by sending a request to APNs using the device token. All this communication needs to happen from some sort of web server.

You can implement your own web service to communicate with APNs, but there are easier options. One of those is Firebase Cloud Messaging (FCM). With Firebase Cloud Messaging (FCM), you have an easy-to-use system at your fingertips! FCM handles the cloud aspect of push notifications, letting you send and receive pushes without having to write your own web service.

In this tutorial, you’ll learn how to:

  • Set up your SwiftUI app to receive push notifications via Google’s Firebase Cloud Messaging using the Swift Package Manager
  • Send notifications with media
  • Receive notifications based on topics
  • Add information to your app from the contents of a push notification
  • Get information about how often push notifications are opened

To follow along with this tutorial, you’ll need:

  • An paid Apple Developer Program membership to use push notifications with your app
  • A Google Firebase account. You won’t need to pay anything to follow along with this tutorial. If you choose to use Firebase in a production environment, you can find pricing information on the associated site.
  • A physical iOS or iPadOS device so you can receive push notifications
Note: While you can test local push notifications on a simulator, you need to be running on a physical device to receive push notifications from FCM.

Getting Started

To get started, select the Download Materials button at the top or bottom of this tutorial.

The app you’ll use, Good News, gives users a news feed of only, well, good news. This is because even though there are bad things happening in the world, it’s important to remember there are good things happening as well!

In the starter folder, open GoodNews.xcodeproj. Open the Project navigator and select the Good News target. In the Signing & Capabilities tab, select your development team and enter a new Bundle Identifier. Build and run on either a real device or in a simulator, for now.

Screenshot of the app running, showing two good news items

You’ll see the app has a feed for showing news articles to users, along with a tab where users can choose to subscribe to topics.

Configuring Firebase

Next, you’ll learn how to configure Firebase.

Creating the p8 Certificate

Firebase requires you to upload a p8 certificate to your app. This is a special file containing a private key that allows Firebase to send notifications. To get a p8 certificate, sign in to Apple Developer.

Select Certificates, Identifiers & Profiles and go to Keys. Select the circle + button to create a new key.

Screenshot showing the 'Keys' heading of the Certificates, Identifiers, & Profiles page, with the + button next to the heading circled in red

Give it a name and enable the Apple Push Notifications service (APNs) service. Select Continue and, on the next screen, select Register.

Creating a new p8 certificate with name

It’s important to note down the following three items from this screen:

  • Select Download to save the p8 file locally. You’ll need to upload this to Firebase. You cannot download this after leaving this screen.
  • Copy and save the Key ID to a file.
  • Copy and save your Apple membership ID. This is next to your name in the upper-right corner of the Membership Center or under Membership Details.

Save your p8 and key ID

Setting up the Firebase Project

Next, go to your Firebase account and select Go to console in the upper-right corner of the page. Select Add project and do the following to create your project:

  • Use the name Good News.
  • Enable Google Analytics.
  • Choose a name and the country for Google Analytics.
  • Use the default analytics settings.
Note: If you don’t need to track how your users interact with push notifications, you can disable Google Analytics and skip setting it up.

Then, you’ll need to configure Firebase with your Apple p8 and membership information. Within your Firebase project, select the gear next to Project Overview and choose Project settings:

Firebase Project Settings

Next, set up an iOS app under the General section of your project settings:

Select the iOS project setup

From there, go to the app configuration page:

  • Add the Bundle Identifier for your project.
  • You can leave App nickname and App Store ID blank if you want.

After registering your app, download GoogleServices-Info.plist. You’ll need this to configure Firebase in your app later. You can select Next for the remaining steps. You won’t be using CocoaPods, and you can ignore the steps for adding initialization code to your app for now. :]

Next, upload your p8 certificate by going to Cloud Messaging in your Firebase project settings. Under APNs Authentication Key, select Upload.

Add p8 file to project

You’ll see a pop-up asking you to:

  • Upload the .p8 file you downloaded from Apple.
  • Enter the Key ID you saved when creating the p8.
  • Enter your Apple membership ID.

Select Upload to finish setting up your Firebase project.

Adding the Package

Now, you’ll use Swift Package Manager to add the Firebase dependency to your project. In Xcode, select File ▸ Swift Packages ▸ Add Package Dependency…. In the Choose Package Repository pop-up, enter https://github.com/firebase/firebase-ios-sdk.git.

Select Next, keeping the default options, until you get to a screen with a list of packages. This might take some time while Xcode downloads the necessary data. Select the following packages from the list:

  • FirebaseAnalytics
  • FirebaseMessaging

If you don’t want to collect information about how your users are using push notifications, feel free to leave FirebaseAnalytics unchecked. After adding these packages, it may take a few minutes to add and build the dependencies.

Note: At the time of writing, the most recent version of Firebase was 7.3.

Next, add GoogleService-Info.plist to your project in the Assets group of your Xcode project:

Add GoogleService-Info plist to project

Finally, you can start adding code to your app!

Configuring Your App

Start by opening Info.plist and adding the following entry:

  • Key: FirebaseAppDelegateProxyEnabled
  • Type: Boolean
  • Value: NO (Xcode will show this as 0)

Screenshot of the FirebaseAppDelegateProxyEnabled entry in the info.plist, with the value 0

By default, FirebaseMessaging uses method swizzling to handle push notifications. You’ll handle all the code yourself, so turn this off using the plist entry you just added.

Next, you’ll add an app delegate to your project, which will be responsible for setting up push notifications. Create a new file called AppDelegate.swift and replace its code with the following:

import UIKit
import Firebase
import FirebaseMessaging
import FirebaseAnalytics

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(
    _ application: UIApplication, 
    didFinishLaunchingWithOptions 
      launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    return true
  }
}

In the code above, you first import the necessary Firebase frameworks and then implement the UIApplicationDelegate protocol.

Next, add a new property to AppMain inside AppMain.swift:

@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

This will make SwiftUI aware of your newly created app delegate. Now you can get started with configuring Firebase.

Launching Firebase

Open AppDelegate.swift again and add the following to application(_:didFinishLaunchingWithOptions:), just before return:

// 1
FirebaseApp.configure()
// 2
FirebaseConfiguration.shared.setLoggerLevel(.min)

Here’s what this does:

  1. It configures your app to work with Firebase.
  2. It sets how much Firebase will log. Setting this to min reduces the amount of data you’ll see in your debugger.

Since you’re not letting Firebase automatically handle notification code through swizzling, you’ll need to conform to UNUserNotificationCenterDelegate. Add the following to the end of AppDelegate.swift:

extension AppDelegate: UNUserNotificationCenterDelegate {
  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    willPresent notification: UNNotification,
    withCompletionHandler completionHandler:
    @escaping (UNNotificationPresentationOptions) -> Void
  ) {
    completionHandler([[.banner, .sound]])
  }

  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
  ) {
    completionHandler()
  }
}

The user notification center will call these methods when notifications arrive or when the user interacts with them. You’ll be working with them a little later.

Registering for Notifications

With Firebase configured, you can start registering to receive notifications. Add the method below to the UNUserNotificationCenterDelegate extension:

func application(
  _ application: UIApplication,
  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
  Messaging.messaging().apnsToken = deviceToken
}

APNs will generate and register a token when a user grants permission for push notifications. This token identifies the individual device so you can send notifications to it. You’ll use Firebase to distribute your notifications, and this code makes that token available in Firebase.

You can now add the following to application(_:didFinishLaunchingWithOptions:), before return:

// 1
UNUserNotificationCenter.current().delegate = self
// 2
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
  options: authOptions) { _, _ in }
// 3
application.registerForRemoteNotifications()

Here’s what the code does:

  1. Sets AppDelegate as the delegate for UNUserNotificationCenter. You implemented the necessary delegate methods in the previous step.
  2. Creates options related to what kind of push notification permissions your app will request. In this case, you’re asking for alerts, badges and sound.
  3. Registers your app for remote notifications.

Next, add the following extension at the bottom of your file:

extension AppDelegate: MessagingDelegate {
  func messaging(
    _ messaging: Messaging,
    didReceiveRegistrationToken fcmToken: String?
  ) {
    let tokenDict = ["token": fcmToken ?? ""]
    NotificationCenter.default.post(
      name: Notification.Name("FCMToken"),
      object: nil,
      userInfo: tokenDict)
  }
}

Messaging is Firebase’s class that manages everything related to push notifications. Like a lot of iOS APIs, it features a delegate called MessagingDelegate, which you implement in the code above. Whenever your app starts up or Firebase updates your token, Firebase will call the method you just added to keep the app in sync with it.

Now, add the following to application(_:didFinishLaunchingWithOptions:), just before return:

Messaging.messaging().delegate = self

This sets AppDelegate as the delegate for Messaging.

You’re close — only one more step to get your app ready. In Xcode, open your app’s project settings and go to Signing & Capabilities. Select the + Capability button. Search for Push Notifications in the field and press Enter.

Add push notification capability

Sending Notifications

Your app is now ready to receive notifications! Build and run on a real device. It should look the same as before, but with an alert asking for permission to send you notifications. Be sure to select Allow.

Screenshot of Good News app showing 'Good News would like to send you notifications' dialog

Now, go to your Firebase project and select Cloud Messaging found under Engage. Then select Send your first message.

Send your first notification

Under Step 1, enter the following:

  • Notification title: Notification Test
  • Notification text: This is a test

Next, pick your app from the drop-down in Step 2:

Send notification step 2

This will target your specific app when using the Cloud Messaging console.

Finally, select Review and then Publish. On your device, you’ll see this notification:

First notification coming through

Note: If you didn’t receive a notification, go back through the instructions to get everything set up. You might have missed a setting or failed to upload your .p8 file. Additionally, you can implement application(_:didFailToRegisterForRemoteNotificationsWithError:) in AppDelegate.swift to print out any possible errors that happen during registering for notifications.

Sending Data in Notifications

Firebase makes it easy to send additional data in your notification through the Cloud Messaging console. For this next section, you’ll add data to your notification payload so you can add a news story to the feed when the notification arrives.

First, in AppDelegate.swift, add the following code inside the UNUserNotificationCenterDelegate extension:

private func process(_ notification: UNNotification) {
  // 1
  let userInfo = notification.request.content.userInfo
  // 2
  UIApplication.shared.applicationIconBadgeNumber = 0
  if let newsTitle = userInfo["newsTitle"] as? String,
    let newsBody = userInfo["newsBody"] as? String {
    let newsItem = NewsItem(title: newsTitle, body: newsBody, date: Date())
    NewsModel.shared.add([newsItem])
  }
}

Here’s what you added:

  1. This gets the information needed in your notification payload.
  2. If userInfo has the values to make a news item, this code creates one and adds it to the news feed of the starter project.

Then, add the following to userNotificationCenter(_:willPresent:withCompletionHandler:) before the call to completionHandler:

process(notification)

userNotificationCenter(_:willPresent:withCompletionHandler:) gets called whenever you receive a notification while the app is in the foreground. This makes sure news items get added when new notifications arrive while a user is using your app.

Then, add the code below to userNotification(_:didReceive:withCompletionHandler:) before the call to completionHandler:

process(response.notification)

userNotification(_:didReceive:withCompletionHandler:) gets called when a user taps a notification. Again, you’ll add a news item whenever this happens.

Build and run. The app will look the same as before, but it’ll be ready to process additional notification data.

Back in the console, create a new notification. Like before, enter the notification title and text and select your app. Then go to Step 5 and add the following items under Custom data:

  • Key: newsTitle
  • Value: Child Saves Kittens

This will send a title to the app. Now, add the entry for the body:

  • Key: newsBody
  • Value: Local child saves kittens from a tall tree.

The keys and values you added will get sent as the userInfo of the notification, which you’ll parse in process(_:). Send the notification. If you have your app open, or if you tap on the notification when it arrives, you’ll see the new news item in the feed.

Screenshot showing a notification that added a news item

It’s also possible to handle notifications from a background process, without any interaction from the user. While this is out of scope for this tutorial, background handling of push notifications and many other topics are covered in our Push Notifications video course as well as the Push Notifications by Tutorials book.

Subscribing to Topics

Another feature of Firebase Cloud Messaging is subscribing to a topic, which is a great way to give your users flexibility in customizing notifications. Firebase lets you tag specific push notifications with topics. You can then subscribe your users to different topics. For instance, maybe users are interested in critical notifications like direct messages but don’t need to be informed of new posts on your app’s feed. Or, you can let your users follow specific posts or people, receive push notifications for group chats, etc.

Fortunately, this is easy to do. First, open TopicsModel.swift and add the following import at the top:

import FirebaseMessaging

The starter project includes a switch that lets users toggle topics they are interested in. You’ll connect those switches to Firebase.

Replace subscribe(to:) and unsubscribe(from:) with the code below:

private func subscribe(to topic: String) {
  // 1
  Messaging.messaging().subscribe(toTopic: topic)
}

private func unsubscribe(from topic: String) {
  // 2. 
  Messaging.messaging().unsubscribe(fromTopic: topic)
}

That’s it! Here’s what you added:

  1. When the user turns a topic switch on, this code will ask Firebase to subscribe to that topic.
  2. When the user turns a topic switch off, this code will ask Firebase to unsubscribe from that topic.

Build and run. Then, go to the Topics tab and turn on the Pets toggle.

Turn on pets subscription

In the console, duplicate the notification from the previous step by selecting the three dots on the far right of the notification:

Duplicate a notification

Leave everything the same, except for Step 2. Change User Segment to Topic, and enter pets for the Message topic:

Set the notification topic

This will now only send notifications to the users who have turned on the Pets toggle. Send the notification, and you’ll see everything work like it did in the previous section. Duplicate the notification again, and change the topic to family and send it. Since your app isn’t subscribed to the topic, Firebase won’t send the notification to your device.

Sending Images

Up to this point, your notifications have all contained only text. But if you’ve received many notifications, you know that notifications can have rich content, such as images. It’d be great if your notifications showed users a nice image related to their content. Once again, Firebase makes this super simple.

Adding a Notification Service Extension

To show an image in push notifications, you’ll need to create a Notification Service Extension. This is a separate target in your app that runs in the background when your user receives a push notification. The service extension can receive a notification and change its contents before iOS shows the notification to the user. You’ll use Firebase to send an image URL inside a notification. You’ll then use a content extension to download the image and add it to the notification’s content.

In Xcode, go to File ▸ New ▸ Target…. Search for Notification Service Extension and select Next. Set the name to Good News Notifications and configure it to add to the project Good News and embed in the application Good News, as in the screenshot below:

Naming the service extension

Select Finish, and when prompted, select Activate.

When you added the Firebase package to your project, it was only added to the Good News target, so now you need to add the necessary dependency to your new extension. Open your app’s project settings and select Good News Notifications under Targets. Under Frameworks and Libraries, select the + button, and search for FirebaseMessaging. Then, select Add. Your project should reflect the image below:

Notification extension setup with Firebase

Customizing Notifications

Now, open NotificationService.swift. This file is where you can customize notifications before the user sees them.

First, add the following import to the top of the file:

import FirebaseMessaging

Next, replace the contents of didReceive(_:withContentHandler:) with the following:

self.contentHandler = contentHandler
bestAttemptContent = request.content
  .mutableCopy() as? UNMutableNotificationContent
guard let bestAttemptContent = bestAttemptContent else { return }
FIRMessagingExtensionHelper().populateNotificationContent(
  bestAttemptContent,
  withContentHandler: contentHandler)

Typically, you’d have to search out the field that contains the image URL, download the image, and then finish the presentation with the image as an attachment. Here, you’re using Firebase’s FIRMessagingExtensionHelper to automatically perform all that work in one simple helper method call.

Keep in mind, iOS only gives you a limited amount of time to download your attached image. If the extension’s code takes too long to run, the system will call serviceExtensionTimeWillExpire(). This gives you a chance to gracefully finish up anything you are doing in the extension or to simply present the notification as is, which is the default implementation.

Ensure Good News is your active target, then build and run. The app should look the same as before.

Back in the Firebase Cloud Messaging console, duplicate the same notification about kittens you’ve been using for the last few steps. Make sure you don’t use the one with the topic set to family, since your app isn’t subscribed to that topic.

This time, in Step 1 under Notification image, add the following URL: https://koenig-media.raywenderlich.com/uploads/2021/01/good_news_petsicon.png.

Your console should appear as below:

Console with image url added

Send your notification. On your device, you’ll see a notification come through with a picture of a kitten, as shown below:

Screenshot showing a 'News Alert: you've got new news!' notification with an image of a kitten on it

Analytics

Now that you’re sending and processing notifications, consider the importance of understanding how users are interacting with your notifications. Are they ignoring them altogether? Do they open them regularly? With Firebase Analytics and Cloud Messaging, you can track the events surrounding your notifications.

Up until now, you’ve been missing one step to processing your notifications: informing Firebase that you received them. In AppDelegate.swift, add the following to process(_:) at the end of the method:

Messaging.messaging().appDidReceiveMessage(userInfo)

Build and run. Then, stop debugging and close your app. Next, duplicate your previous notification in the Cloud Messaging console and send it. When you get the notification, tap it to open your app.

Open the notification in the Cloud Messaging console. It will show a count for Sends, as below:

Notification open count is empty

Tracking Custom Events

Now that your app is informing Firebase that it received the notification, there might also be a count for Opens. However, it’s unlikely you’ll see this number increase. It can take up to 24 hours before you see analytic information get processed and presented to you in the Firebase console. In fact, you may never actually see this number increase at all, even after that. At the time of writing, there are limitations on what Google can report back for iOS and Android notifications. That’s OK, though — you can still take advantage of Analytics and track custom events.

Note: This section of the tutorial is not required for push notifications to work. If you’re not interested in tracking how your users interact with push notifications, feel free to skip this section. If you skipped setting up analytics earlier, you’ll need to add the required packages as well as set up your Firebase app for analytics before continuing on.

In AppDelegate.swift, add this line of code in process(_:), right after NewsModel.shared.add([newsItem]):

Analytics.logEvent("NEWS_ITEM_PROCESSED", parameters: nil)

This code uses Firebase’s Analytics to track that you added a news item to your feed. Now, add this line of code, right after Messaging.messaging().appDidReceiveMessage(userInfo):

Analytics.logEvent("NOTIFICATION_PROCESSED", parameters: nil)

This logs an event saying that you processed the notification, whether or not it contained a news item.

Build and run. Once again, duplicate your notification in the Cloud Messaging console and send it. Back in Firebase, choose Events under Analytics. Here, you’ll start to see your events show, like below:

Analytics for notifications

In this example, you only see an entry for NEWS_ITEM_PROCESSED. This is because Google hasn’t yet finished processing the analytics for the app. Again, it’s unlikely you’ll see either of these events logged in your account as you follow along with the tutorial. Let several hours to one day pass, and check again.

Where to Go From Here?

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

Note: You’ll need to add GoogleService-Info.plist to the final project. It isn’t included in this download, as it could contain sensitive information.

You’re now able to send and receive notifications using Firebase Cloud Messaging! You’re also acquainted with Firebase Analytics, which you can learn more about from Google or Firebase Analytics: Getting Started.

If you’re looking how to get started with push notifications on Android, check out the Firebase Cloud Messaging for Android: Sending Push Notifications tutorial.

To learn more about push notifications on iOS without Firebase, check out Push Notifications Tutorial: Getting Started.

If you want to get deep into push notifications and find out everything they offer, check out our Push Notifications video course as well as the Push Notifications by Tutorials book.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!

Average Rating

4.3/5

Add a rating for this content

6 ratings

More like this

Contributors

Comments