Push Notifications and Local Notifications - Tutorial
By using notifications, you can notify the user about important events even when the app is not running. In this tutorial you will learn how you can implement both local and push notifications.
Hint: This tutorial is using Xcode 9 and iOS 11
Generally speaking, there are two kind of notifications: Local notifications and push notifications (also called remote notifications). Locale notifications are scheduled by the app and can be triggered by time, date or place. Push notifications on the other hand are sent by a backend. This can be your own backend, but also a cloud service your are using. The handling of notifications have been massively simplified with the introduction of the Messaging framework in iOS 10. In this tutorial we will speak about both local and push notifications. We assume, that your app is targeting at least iOS 10.
[toc]
Video
The whole tutorial is also covered in this video:
Local notifications
We start by looking at local notifications. They are easier to handle than push notifications and are a good foundation for understanding push notifications.
Requesting permission
Before your app can send any type of notification, you have to request the permission from the user to do so. This makes a lot of sense because notifications are very prominent. If the user wants to receive notifications from your app, that’s a good thing. But if the user didn’t have the possibility to disable notifications for an app, they could be too much of a distraction.
Normally, you request the permission in the applicationDidFinishLaunchingWithOptions method of your app delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in print("granted: (\(granted)") } return true }
For that, you also have to import the UserNotifications framework in the header of the AppDelegate:
import UserNotifications
The requestAuthorization method triggers an alert view, which asks the use whether he allows the app to send notifications. The first parameter of the method takes an array of options. These options indicates which kind of notifications your app is using. In our example, these are alerts, sounds and the badge indicator at the app icon. Additionally, also CarPlay is available. The second parameter is a block, that is called after the user has responded to the alert view. The granted parameter tells you how the user has decided.
If the user gave the permission, your app is now ready to schedule and send notifications.
Sending local notifications
For sending local notifications you basically need three objects: the content, a trigger and a request. Let’s start by looking at the content:
let content = UNMutableNotificationContent() content.title = "Title" content.body = "Body" content.sound = UNNotificationSound.default()
This is a very basic notification, that has just a title, a body text and the default sound. You can of course add a lot more to the notification, like a badge counter information, a custom sound or additional information, that can be accessed by the app when the user taps on the notification.
Next, we need a trigger:
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
This is an instance of UNTimerIntervalNotificationTrigger, that lets you define a time interval when the notification should be fired. In this case, these are five seconds. You can also specify whether the notification should be fired repeatedly. Besides that, there are two additional triggers available:
- UNCalendarNotificationTrigger : With this trigger you can specify date and time for the notification.
- UNLocationNotificationTrigger : Here you can specify a location. When the user enters that region, the notification will be fired.
The last object we need is a request:
let request = UNNotificationRequest(identifier: "TestIdentifier", content: content, trigger: trigger)
The request just needs the content, the trigger and an identifier. The identifier is used to identify the notification. If your app sends another notification with the same identifier, the first notification will be overridden by this new notification.
Now we have everything to schedule the notification:
UNUserNotificationCenter.add(request, withCompletionHandler: nil) center.add(request, withCompletionHandler: nil)
For testing purposes, we place the call in the viewDidLoad method of a view controller. After the app starts, we leave the app before the five seconds are over because without additional work the notification will only be visible when the app is not in the foreground. The complete code looks like this:
import UIKit import UserNotifications class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let content = UNMutableNotificationContent() content.title = "Title" content.body = "Body" content.sound = UNNotificationSound.default() let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) let request = UNNotificationRequest(identifier: "TestIdentifier", content: content, trigger: trigger) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) } }
It’s that easy!
Displaying notifications when the app is in the foregorund
As said before, the notification will not be visible when the app is in the foreground. For that, the app delegate has to implement the UNUserNotificationCenterDelegate. This protocol has two optional methods. We will implement the following one:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert, .sound]) }
Only when the completion handler is called, the notification will also be displayed when the app is in the foreground. In order for this to work, we also have to set the delegate in the didFinishLaunchingWithOptions method:
UNUserNotificationCenter.current().delegate = self
Before iOS 10, it wasn’t possible to display notifications automatically when the app was in the foreground. Instead, you had to display them by using a custom implementation.
Handling notifications
When the user taps on a notification, he expects the application to open and perform some kind of action. That the app opens happens automatically. If you want to react to a notification, you have to implement some custom behaviour. For this, we implement another method of the UNUserNotificationCenterDelegate:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { if response.notification.request.identifier == "TestIdentifier" { print("handling notifications with the TestIdentifier Identifier") } completionHandler() }
Inside this callback you can for example launch a specific screen.
Push notifications
Now you know the basics of local notifications. It’s time to look at the hard stuff: push notifications. Let’s start by talking about the general process of sending and receiving push notifications.
How do push notifications work?
Generally speaking, there are four participants in the whole push notifications workflow: The app, the device the app is running on, a backend and the Apple Push Notification Service (APNS). Take a look at the general workflow:
- First, the app requests to get registered for remote notifications.
- The device hands the request over to the APNS
- When the registration was successful, the APNS sends a token to the device.
- The device hands the token over to the app.
- The app sends the device token to the backend (or some kind of cloud service), so that the backend knows how it can identify the device.
- When the backend wants to send a notification to a device, it sends the message and the corresponding device token to the APNS.
- The APNS sends the notification to the device.
So this looks quite complicated, but don’t worry. We will take a look at each step.
Requirements for your app to receive push notifications
You need to be enrolled in the Apple Developer Program in order to be able to create apps that can receive push notifications. Since this is also a requirement for publishing to the Apple App Store, you are probably enrolled anyway.
Furthermore, the push notification feature has to be added to your app id and the push notifications entitlements have to be set in your entitlements file. All this is Xcode doing automatically for you. For that, make sure that your are logged in. You can do this in Xcode -> Preferences…-> Accounts. Then, you can go to the “Capabilities” tab inside your target settings. And now you can activate push by toggling the corresponding button:
You can do this of course also manually by logging into your Apple Developer Program using a browser. Using Xcode is more convenient though.
Registration for push notifications
As with local notifications, your app have to request the permission from the user to receive push notifications. It’s working the same way as with local notifications. Then, it has to register for push notifications (I):
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { UNUserNotificationCenter.current().delegate = self UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in print("granted: (\(granted)") } UIApplication.shared.registerForRemoteNotifications() //(I) return true }
As you can see, you just have to call the registerForRemoteNotifications.
Receiving the push device token
If the registration for push notifications was successful, the APNS will you send a push device token. To receive this token, you have to implement two methods of the UIApplicationDelegate, which happens usually in the AppDelegate:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() print("token: \(token)") } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("failed to register for remote notifications with with error: \(error)") }
As you can see, one method is called if the registration was successful, and the other one is called if it wasn’t.
Handing the push device token over to the backend
The backend has to know the device token so that it can identify the device. So it’s the job of your app to send the device token to the backend. How this works depends on the backend you are using. The implementation of this kind of backend is not part of this tutorial. For example, you can do this by using a Node.js backend.
There is something you have to consider: The callback for receiving the device token will be called every time the app gets started. In most cases, the device token doesn’t change – it’s possible though that it does! So your backend has to have some kind of service for updating the device token.
For testing purposes, we will simulate the backend by using a Mac OS client.
Creating push certificates
It doesn’t matter what kind of backend you are using – you always need a so-called push certificate, so that your backend is able to identify itself to the APNS. We need this certificate also for the Mac OS client, that will simulate the backend. You can create the push certificate in the Apple developer portal.
After you have logged in, go to the “Certificates, Identifiers & Profiles” section. Then choose “App IDs”. Xcode should have already created your App identifier. Select it and click “Edit”. Now you can activate “Push Notifications” and create the certificates by following the instructions:
There are two types of certificates: a development certificate and a production certificate.
Sending push notifications
Now we are ready to send a push notification! There are several applications you can use to send a push notification. In this tutorial we will use the Mac application Pusher, which you can download here.
Since your push certificate is already in your key-chain, Pusher can read them out und you can choose the right one.
Then, you have to paste your device token inside the text field. You can copy it from the Xcode console after starting your app on a device.
There is already a demo payload, which is a JSON, that is used to specify all the information the device needs to present the notification. It contains for example the message that should be displayed. The payload looks like this:
{"aps":{"alert":"Testing.. (30)","badge":1,"sound":"default"}}
This is enough for performing a test, so you can click on “Push”. Now the notificication is sent to the APNS and your device should receive a push notification on your device.
Congratulations, you have sent your first push notification!
Handling the result
Your application can handle the result exactly as with local notifications. But it’s also important that you have access to the payload, which can contain important information the server is sending to the app. Here’s an example how you can access it:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { print("handling notification") if let notification = response.notification.request.content.userInfo as? [String:AnyObject] { let message = parseRemoteNotification(notification: notification) print(message as Any) } completionHandler() } private func parseRemoteNotification(notification:[String:AnyObject]) -> String? { if let aps = notification["aps"] as? [String:AnyObject] { let alert = aps["alert"] as? String return alert } return nil }
In this case, the alert string from the payload is parsed and printed to the console. In the payload there can also be very app specific information. This can be for example some kind of identifier, that your app can use to display specific content after the user has tapped on the notification. Here’s an example how such a payload can look like:
{"aps":{"alert":"Testing.. (30)","badge":1,"sound":"default"}, "identifier" : "12345"}
In this example, an ID is contained inside the payload. You can parse it similar to the example above:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { print("handling notification") if let notification = response.notification.request.content.userInfo as? [String:AnyObject] { let message = parseRemoteNotification(notification: notification) print(message as Any) } completionHandler() } private func parseRemoteNotification(notification:[String:AnyObject]) -> String? { if let identifier = notification["identifier"] as? String { return identifier } return nil }
References
Apple: Local and Remote Notification Programming Guide
Title image: @ chainarong06 / shutterstock.com