Handling iOS Push Notifications: The Not So Apparent Side

Oscar
Oscar
Sep 7, 2018 · 8 min read

At Fenrir, many of our developers have had the experience of maintaining applications that have been around for a while. Because of the scale of some of these apps, supporting older iOS versions is often a requirement. When you also consider the fact that a small mistake in the migration could possibly impact thousands of people, understanding what’s going on with the code becomes a necessity. Push notifications is one of those technologies that can be hard to test because of the setup required.

Since push notifications have been around for quite a while, this article will not talk about how to implement them. Instead, it will try to clarify various aspects about push notifications that might not be so apparent to newcomers or might have even been overlooked by some veterans. One of them being the timing in where each of the handler methods is actually called.


Configuration

Before push notifications can be received there are 2 things that must be properly configured.

The Device Capabilities

The app must specify what capabilities it supports. To receive push notifications, the “Push Notifications” capability must be turned on.

Another capability that can also be turned on is “Remote notifications”. This one is found under the “Background Modes” group and will allow your app to be launched in the background in response to a notification whose payload includes the “content-available” flag (aka Silent Notifications).

The Device Token

Getting a device token is necessary so that your server can use it to send notifications through apple’s distribution system.

The device token can be retrieved by calling this method:

Which will return the appropriate response in your AppDelegate methods.

However, the timing in where this method should be called depends on the device capabilities currently set.

That is, if you don’t have the “Remote notifications” capability turned on, you will have to ask for the user permission to display alerts before being able to retrieve the device token. But if it is on, you can retrieve the device token at any time.

Asking the user for permission is done with the following methods.

Before iOS 10, using the UIApplication method:

  • registerUserNotificationSettings(_:)

iOS 10 onwards, using the UserNotifications framework methods:

  • requestAuthorization(options:completionHandler:)

Notification Types

Something that is often overlooked is that there are actually 2 types of push notifications.

“Alert Notifications”

These notifications are used to alert or inform the user about something. They can include an alert, set a badge and even play a specific sound.

  • Only the “Push Notifications” capability is required.

Their payload is similar to this (example taken from the apple documentation):

{
"aps" : {
"alert" : "You got your emails.",
"badge" : 9,
"sound" : "bingbong.aiff"
},
"acme1" : "bar",
"acme2" : 42
}

Important:
If the payload doesn’t include at least one of the following 3 keys, “alert”, “badge”, “sound” then the push notification will be ignored. (No methods will be called inside your app)

“Silent Notifications”

These notifications are used to execute code in response to a push notification event. They do not show an alert. They also work when your app is on the background sleeping, or when it isn’t running (was killed by the system). In these cases it will be awaken/relaunched and given up to 30 seconds of execution time.

  • The “Remote notifications” capability must be set in addition to the “Push Notifications” one.

The payload for silent notifications must include the “content-available” flag set to “1”.

{
"aps" : {"content-available" : 1},
"acme2" : [ "bang", "whiz" ]
}

Important:
Your app won’t be launched if it has been killed by the user.

Mixed Push Notifications

This is not a notification type per-se but it’s important to mention that the payload of both notification types can be mixed.

{
"aps" : {
"alert" : "You got your emails.",
"badge" : 9,
"sound" : "bingbong.aiff",
"content-available" : 1
},
"acme1" : "bar",
"acme2" : 42
}

Important:
Just mixing the payload is not enough. You still have to configure and implement the required methods for each of the types independently.


The Methods

When a push notification arrives there are 4 methods that can be used to handle the notification. Which method gets called is defined by the following factors:

  1. The currently implemented methods.

The next section will try to explain this behavior. For simplicity, the different application states will be summarized like this:

  • Foreground: The app is currently shown on the user’s screen.

And the notification payload type will be summarized like this:

  • Alert Payload: Contains a least one of the following values “alert”, “badge”, “sound”.

Finally, the methods can be categorized in 2, based on where they should be implemented.


The AppDelegate Methods

These methods can be used pre iOS 10. They are implemented inside the application’s AppDelegate class since they are part of the UIApplicationDelegate protocol.

application(_:didReceiveRemoteNotification:)

The oldest among the methods when handling push notifications.

Things to consider:

  • It was deprecated on iOS 10.

With an Alert Payload it is called in the following situations:

  • Foreground: When the notification arrives. (Doesn’t show a system notification)

With a Silent Payload it is called in the following situations:

  • Foreground: When the notification arrives.

With a Mixed Payload it is called in the following situations:

  • Foreground: When the notification arrives. (Doesn’t show a system notification)

application(_:didReceiveRemoteNotification:fetchCompletionHandler:)

The purpose of this method is to handle silent notifications, but it can also be used if the UserNotifications framework is not available (pre iOS 10).

Things to consider:

  • application(_:didReceiveRemoteNotification:) won’t be called if this method is implemented.

With an Alert Payload it is called in the following situations:

  • Foreground: When the notification arrives. (Doesn’t show a system notification)

With a Silent Payload it is called in the following situations:

  • Foreground: When the notification arrives.

With a Mixed Payload it is called in the following situations:

  • Foreground: When the notification arrives. (Doesn’t show a system notification)

Important¹:
Under normal circumstances, the “content-available” flag should launch your app if it isn’t running and wasn’t killed by the user. However, this is ultimately decided by the system so it might not always happen. In that case, this method will be called the next time the user launches your app.


The UserNotifications Framework Methods

This framework is only available when the deployment target is set to iOS 10 or newer. An object conforming to the UNUserNotificationCenterDelegate protocol should be used. This object must be set as the UNUserNotificationCenter’s delegate before the app finishes launching.

You must assign your delegate object to the UNUserNotificationCenter object no later before your app finishes launching. For example, in an iOS app, you must assign it in the application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions: method.

The protocol contains two methods which are used to handle incoming push notifications:

  • userNotificationCenter(_:willPresent:withCompletionHandler:)

Once both methods are implemented, the AppDelegate method application(_:didReceiveRemoteNotification:) won’t be called anymore.

Important:
The UserNotifications framework is only used to handle Alert Payloads and thus, if you want to handle Silent Payloads, you will have to implement the application(_:didReceiveRemoteNotification:fetchCompletionHandler:) method as well.


Understanding when each of these methods gets called can be done by considering two situations.

  1. If the app is currently on the foreground willPresent” will be called as soon as the notification arrives. You can control whether to display an alert or not based what you return on the method’s completion handler.

The Launch Options Dictionary

Regardless of the whatever methods are currently implemented, if the application was launched as a result of a notification, either by the user tapping on it, or the payload having the “content-available” key, the “launchOptions” dictionary will contain the payload of the notification, which can be accessed through the “.remoteNotification” key.

In other words, you can test if the “launchOptions” dictionary contains a value for the “.remoteNotification” key in case you want to update your interface in response to a notification if your app wasn’t running when it arrived. (Similar to how messaging apps can go straight to a conversation thread when they are launched through a notification)

Conclusion

Even though things have gotten much simpler since the UserNotifications framework was introduced, it can still be useful to know how things used to work before its existence. Especially when having to maintain old projects that still use the old AppDelegate methods or when performing a migration to the UserNotifications framework.

Unfortunately push notifications still remain as one of those technologies that are not quite easy to test and experiment with. We hope this article could offer some clarification about the way they work.


Stories by Fenrir Inc.

Tech articles and developer stories written by Fenrir's designers and engineers.

Thanks to Javier Garcia

Oscar

Written by

Oscar

iOS Developer in Japan.

Stories by Fenrir Inc.

Tech articles and developer stories written by Fenrir's designers and engineers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade