Let’s consider that you have developed a mobile application that consumes information from a server about many store’s prices and discounts in the products they offer. Today you got a new store as a client, and that store has most of its products with a 30% discount due to inauguration. How do you notify the users of your application about this new store? That’s when push notifications come into play.

Push notifications are a brief message sent by a server or application to the user’s mobile device even if the user is not actively interacting with it. Push notifications improve the engagement, communication with the application and motivates users to interact with it.

This post will guide you through the setup process needed for your project to enable the “push notifications” feature for your app on iPhones. We’re going to go over the required settings that your project must have and the basic code that you’ll need to create to get these push notifications working.

Setting up

The capabilities [1] allow the project to access some services that apple provides, as to this exercise we will be adding 2 of them: Push notifications and Background Mode.

On Xcode:

  1. Select the .xcproject file in your Project Navigator.

  2. Select your application under Targets, then select the Signing & Capabilities tab.

  3. Click on the button + Capability that is placed in the top left and then search and add Push Notifications. Xcode automatically registers your application for push notifications when you add this capability.

  4. Still on the Signing & Capabilities tab, add the Background Modes capability too. Then in the Background Modes, enable Remote notifications. This option causes your application to be woken up if it’s running in the background when a notification arrives.

Requesting required permissions

Even though push notifications are meant to provide updates or information that is considered relevant to the user and increase engagement and communication, you must request permissions for notifications to be shown, because excessive notifications could annoy the user and be invasive for them.

To request notification permission we need to invoke a method named requestAuthorization(options:completionHandler:)[2] from the UNUserNotificationCenter class, and is typically done in the AppDelegate’s method application(_:didFinishLaunchingWithOptions:).

let options: UNAuthorizationOptions = [.alert, .sound, .badge]
UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in
   if granted {
       print("Permission for push notifications allowed!")
   } else {
       print("Permission for push notifications denied.")
   }
}

Right after the piece of code is called, the user will be prompted to allow or deny notifications, afterwards, the OS will ignore further calls of the requestAuthorization method.

It’s important to note that:

  • After the initial prompt of the alert, if the user denies it, the alert window will not show up again, this is in aims to protect the user’s privacy, and avoid poor UX.
  • if the user denies the permission, and this permission is important for the application to work property, you should state the reasons why this should be allowed and instruct the user to open the settings and enable the permission manually.

Registering for Push Notifications

In order to use push notifications, is necessary to register the application with the Apple Push Notification Service (APNs).

APNs[3] is a service that allows third-party servers to send notifications to apple devices regardless of their operating system (tvOS, iOS, watchOS, and macOS). The server should be configured properly to communicate with APNs. Part of the process of configuring the server should include an APNs certificate from an apple developer account.

After the user grants permission to use remote notifications the registerForRemoteNotifications()[4] method from the UIApplication class needs to be called. This is usually made after you handle the decision where the permission request is made:

if granted {
   DispatchQueue.main.async {
       UIApplication.shared.registerForRemoteNotifications()
   }
}

The method UIApplication.shared.registerForRemoteNotifications() should be called in the main thread, or you’ll receive a runtime warning. This is simply made to indicate the Application wants to receive remote notifications.

To handle the registration process, two methods need to be implemented in the AppDelegate class:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
   // Send deviceToken to your remote server.
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
   // Handle the error.
}

If the registration process succeeds, the application(_:didRegisterForRemoteNotificationsWithDeviceToken:) is going to be called and a deviceToken will be provided. This device token is the unique identifier of the device in which the application is installed, and should be sent to the server, therefore if it fails, the application(_:didFailToRegisterForRemoteNotificationsWithError:) is called.

Handle push notification

When the application receives a notification, the UIApplicationDelegate[5] is called, and this notification is handled differently according to the state of the application, if the application is running in background or foreground or if is not running at all.

1. Closed Application

Whenever the application is not running and receives a push notification, the operating system will present the notification to the user, however if the notification is tapped, the application is launched and you can access the notification payload that is in the method application(_:didFinishLaunchingWithOptions:).

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  
   if let notification = launchOptions?[.remoteNotification] as? [String: AnyObject] {
       // application started due to a tap on a notification. You can extract the notification payload to handle it.
   }

   return true
}

2. Application running in background state

if the application is running in background state, the application(_:didReceiveRemoteNotification:fetchCompletionHandler:) from the AppDelegate is called, and if needed you can fetch data.

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
   // Process the notification, call the completionHandler whenever you are done
   completionHandler(.newData)
}

3. Application running in foreground state

if the application is running in foreground state, the UNUserNotificationCenterDelegate method willPresent is called.

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
   // Here you can handle the notification's payload
   completionHandler([.alert, .sound])
}

Generally the UNUserNotificationCenterDelegate[6] is implemented in a separate class and is set to UNUserNotificationCenter.current() as a delegate, this commonly happens in the application(_:didFinishLaunchingWithOptions:) method.

Receiving push notifications

When the application receives a push notification, it comes with a structure in which the properties have different utilities:

{
   "aps" : {
       "content-available" : 1,
       "alert": {
           "title" : "notification title",
           "subtitle" : "notification subtitle",
           "body" : "description of the notification",
       },
       "badge":1,
       "sound": "default",
   },
   "custom_key": "small amount of data",
   "Simulator Target Bundle": "you application bundle name"
}

Going over the Json structure

  • If the notifications come with a "content-available" : 1, means that is a silent notification, this notification can wake up your application to perform tasks in the background like fetch data, and keep the information of the application refreshed. "content-available" property dictionary is meant to include a value of 1 to indicate that new content is available. The system doesn’t define or expect any other values. Other values are likely to be treated the same as if the key was not present.

  • "title", "subtitle" and "body" refer to the push notification content that will be displayed to the user. In the content not only you can show text describing briefly what the notification is about, but also you can show images.

  • "badge", this property modifies the number displayed in the top-right corner of the application icon.

  • The property "sound" comes with the name of the sound that will be reproduced when the notification arrives, if its value is default, the system default sound will be played, or it can be the name of a file that should be already inside the project folder in the /Library/Sounds or in the main bundle of the current executable. If this property is not coming in the payload, no sound will be played at all.

  • You can also add custom keys to the payload, to send to the device information.

  • If you are testing the push notification locally, the property "Simulator Target Bundle" should be added, so your simulator knows to which application it sends the notification to.

So far, we have the configuration needed for the project, the required permissions, a brief explanation of the payload, and the necessary code to handle the information coming through the push notification. Let’s test it in a simulator!

Testing the Implementation in a Simulator

  1. Create a new file with a .apns extension.

  2. In the file, write a json structure similar to the one we have before, and fill it with the information you need.

  3. Run the application in a simulator.

  4. Drag and Drop the file onto your simulator.

  5. Push notification should appear.

Overall, push notifications are a powerful tool for engaging users and delivering relevant information. They are incredibly versatile, with use-cases ranging from announcing new features in an application to providing real-time updates. Whether you’re using them to drive engagement, deliver news, trigger immediate actions, or something else, push notifications can be tailored to meet your unique needs.

References

  1. Capabilities
  2. Request permission
  3. Apple push notification (APNs)
  4. Register application for push notification
  5. UIApplicationDelegate protocol
  6. UNUserNotificationCenterDelegate protocol