Push Notifications

Janvi Arora
6 min readFeb 28, 2023

--

Notifications can display an alert, play a sound, or badge the app’s icon. 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.

By default, if an iOS app gets a notification while the app is in the Foreground, the notification banner won’t show up.

But as far as iOS 10 and further versions are concerned, we can override this behaviour and make the notifications visible when the app is in the foreground.

Reason for default behaviour of push notifications:

Before iOS 10, if a notification arrived while an app was in the foreground, the system would silence that notification.

Apple thinks the default notification banner is redundant since a user is already in the app.

They want us to update our app’s interface directly. For example, if a new message arrives, you need to update the badge count in your tab bar.

Since iOS 10, Apple has changed its stance on this behaviour and now gives us the option to choose between Silencing an incoming notice (which is the same as it was before iOS 10) or instructing the system to keep displaying the notification UI as push notification banner.

Functionalities provided via Push Notifications:

  • 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 the code to change your notification before displaying it.
  • Display a custom, interactive UI for your notification.
  • And probably more.

NOTE: Sending push notifications is the 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).

How to show push notifications when app is in foreground state?

Tasks involved:

  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.

Steps involved in Code:

  1. Add the UNUserNotificationCenterDelegate protocol to your ViewController or AppDelegate.
class ViewController: UIViewController, UNUserNotificationCenterDelegate {
// ...
}

2. Set the delegate for the notification center.

UNUserNotificationCenter.current().delegate = self

3. Implement the userNotificationCenter(_:willPresent:withCompletionHandler:) method to handle the notification.

func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// Show the notification banner when the app is in the foreground state
completionHandler([.alert, .badge, .sound])
}

Implementation in AppDelegate:

import UIKit
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
registerForPushNotifications()
return true
}

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}

extension AppDelegate {
private func registerForPushNotifications() {
/// Setting the delegate so we can receive delegate callbacks
UNUserNotificationCenter.current().delegate = self

/// Requesting authentication from user
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in

/// 1. Check to see if permission is granted
guard granted else { return }
/// 2. Attempt registration for remote notifications on the main thread
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}

extension AppDelegate: UNUserNotificationCenterDelegate {
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(error)
}
}

Explanation:

  1. UNUserNotificationCenter handles all notification-related activities in the app, including push notifications.
  2. You invoke requestAuthorization(options:completionHandler:) to 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.
  4. _(didRegisterForRemoteNotificationsWithDeviceToken:)_ 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.

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.

What happens when device receives 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.

NOTE: 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.

Push notifications are not guaranteed to arrive. 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).

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.

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

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

UNUserNotificationCenter.current().setNotificationCategories([category])
  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 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.
// MARK: - UNUserNotificationCenterDelegate
// Handling notification actions

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.

--

--

No responses yet