iOS 10 Notifications with Attachments and Much More

Prianka Liz Kariat
11 min readDec 12, 2016

--

This is the second installment of a two part article on iOS 10 notifications. Today we’ll go through the technical details on how to implement notification attachments and many more sophisticated features introduced in iOS 10.

For starters Apple has moved notification handling to a new framework named UserNotifications. Please refer https://medium.com/@prianka.kariat/ios-10-notifications-an-overview-8e88b7f3436b#.1yk3ij8j4 for new features in iOS 10 notifications.

This article will be divided as follows

  1. Permission Handling
  2. Local Notifications with Attachments and Notification Handling
  3. Remote Notifications with Attachments
  4. Customizing the UI for notifications

1. Permission Handling

For your app to be able to send notifications to the device, it should ask permissions to the user. This can be done as illustrated below using UserNotifications framework.

func requestAuthorization(options: UNAuthorizationOptions = [], completionHandler: @escaping (Bool, Error?) -> Void)

will request the user permissions to send notifications related to the app. UNAuthorizationOptions let you define which permissions you want to grant to the app.

Registering Notification Categories

An app might receive different types of notifications. As you are aware iOS notifications can include actions which let you perform a functionality on a button click, or even comment and reply to a text message. Actions look like this:

Different notifications might have different actions. If you can send text messages as well as an email conversation from your app, the actions you have to respond to when you receive each of these kinds of messages would be different. Hence we have notification categories. Later on you will learn that these categories are also important when you want to implement custom interfaces for different notifications.

I have created 3 categories, “local”, “recipe” and “email” based on the 3 types of notifications I need.

“local” will be used to illustrate scheduling a local notification which has only a like action, “recipe” for a recipe feed notification on which users might be able to comment in addition to liking the feed, an “email” notification which has “archive” and “reply” actions.

You can see that all actions which are simple button clicks are of the type UNNotificationAction sparing comment action which is of type UNTextInputNotificationAction which will present a textfield docked above the keyboard.

2. Local Notifications with Attachments and Notification Handling

Sending Local Notifications

Let us look at how you can send local notifications from your app using UserNotifications.

There are 4 steps to sending a local notification.

  1. Create the notification content
  2. Create the trigger
  3. Initialize the notification request with the created trigger and content.
  4. Add the notification request to the UNUserNotificationCenter.
  1. Create the notification content

UNMutableNotificatonContent encapsulates the content associated with the notification.

var categoryIdentifier: String — indicates to which category the notification belongs. We have selected the “local” category. So the notification is expected to have one action “like”.

var attachments: [UNNotificationAttachment] — This specifies any attachments you want to make to the notification like an image or video. We specify the url to an image on the device.

UNNotificationAttachment encapsulates your attchment.

2. Create the trigger

Local notifications need to be triggered. Triggers are encapsulated by UNNotificationTrigger.

There are 3 types of triggers:

  1. UNTimeIntervalNotificationTrigger — Schedules the notification after the specified time interval.
  2. UNCalendarNotificationTrigger — Schedules the notification for a specified date.
  3. UNLocationNotificationTrigger — Triggers the notification when a user enters a location specified by CLRegion.

I will be using a UNTimeIntervalNotificationTrigger as example.

3. Initialize the notification request with the created trigger and content.

You have to initialize UNNotificationRequest with a unique identifier for your notification.

var identifier: String — Important as you will see later that this identifier will be used for updating an existing notification or removing it. So carefully choose your id.

4. Add the notification request to the UNUserNotificationCenter.

This call will schedule your notification with the notification center.

The generated notification looks somewhat like this

UNUserNotificationCenterDelegate

Your app should implement this delegate to receive a notification in the foreground and to do any processing if the user opens the app after dismissing the notification or clicking on an action button.

I have a button which can remove the notification. I am going to enable the button only after user has sent notification using the “Send” button.

This delegate method gets called whenever the app is opened in the foreground after receiving the notification.

Receiving Notifications While App is In Foreground

Earlier iOS did not have a provision to receive notifications for a specific app when app was in the foreground. This can now be achieved. Your app needs to implement one of the methods of UNUserNotificationCenterDelegate.

This code is the bare minimum required for the presentation of the notification when your app is in the foreground. You can do any additional processing in the app. In addition to the above method,

func userNotificationCenter(UNUserNotificationCenter, didReceive: UNNotificationResponse, withCompletionHandler: @escaping () -> Void)

will also be called.

Updating Notifications

Updating notifications requires you to schedule a notification request with UNUserNotificationCenter. This notification request should have the same identifier as the notification you want to update.

We have a textfield which lets the user enter the body of the notification. We are only sending notifications with a single identifier. So when a new notification is received, it will update the existing notification rather than cluttering the notification center.

We have specified a 10 sec interval for the notification trigger. If user chooses to send a notification with a modified body before the notification is delivered, the old notification scheduled with the notification center will be updated.

Removing Notifications

You can remove delivered notifications as well as pending ones sent by the application.

As soon as the user chooses to send a notification, we enable the remove button. If the notification has already been delivered, it is encapsulated by a UNNotification. You can get a list of delivered UNNotifications using the method,

func getDeliveredNotifications(completionHandler: @escaping ([UNNotification]) -> Void)

UNNotification encapsulates the UNNotificationRequest we created earlier. We had assigned an identifier “localIdentifier” for our local notfication. We can use the same to filter and check if our notification is deliverd.

We can remove a delivered notification using:

func removeDeliveredNotifications(withIdentifiers: [String])

Now what if our notification is in the pending list? If that is the case the notification still remains as a UNNotificationRequest since it hasn’t been delivered. We can find out a list of pending notifications using:

func getPendingNotificationRequests(completionHandler: @escaping ([UNNotificationRequest]) -> Void)

You can remove a pending notification by:

func removePendingNotificationRequests(withIdentifiers: [String])

3. Remote Notifications with Attachments

The method for enabling your app to receive remote notifications is not unfamiliar. You can refer to the APNS guide if you need detailed information on how remote push notifications work.

From an app developer’s perspective, following are the steps to make your app capable of receiving remote push notifications.

  1. Visit the apple developer portal and create the push notification certificate for your app id. This will be required for your server to add capabilities to send push notifications for the specific app id.
  2. Turn on “Push Notifications” in your app’s capabilities from Xcode.This adds an entitlements file to your bundle.
  3. Register the device to obtain a device token, as soon as you want the app to start receiving push notifications.

Device Token Generation

Most of you would be familiar with

UIApplication.shared.registerForRemoteNotifications()

This call initiates registering of your device with the apple servers to recieve notifications for your app. It generates a device token which should be sent in the payload when your app’s server contacts the Apple server to send a notification to the device.

Please modify your logic in permission handling so that as soon as the user agrees to receiving notifications sent by the app, you register the device for receiving remote notifications.

You can use the AppDelegate method to obtain the device token after registration.

This is usually sent to the server. Since I am using a 3rd party library for sending push notifications, I just take note of the device token. I’ll elaborate upon the 3rd party library I am using towards the end of this article.

Attaching Files to Remote Notifications

Attachments can also be made to remote notifications. But as most of you would be aware, maximum payload that can be sent with a push notification is 4kb. Even if we are attaching a very low resolution image or a short video clip it will not be as small as 4kb. So how do we attach files of the order of few mbs ? The answer is a Notification Service Extension.

The files are attached after the notification is delivered to the device and before it is displayed to the user in a service extension.

Let us head straight to the Xcode project and create our service extension.

Attachments Should be Small !!

Keep your attachments at minimum size, especially if content is being downloaded. The service extension will be terminated after a short time interval by the OS. So you have to make sure you complete your processing before the allotted time.

We are going to show a recipe feed as a notification to the user.

This notification will have the following parts.

  1. Title of the recipe
  2. Subtitle — who has posted the recipe
  3. Body — a short description of the recipe
  4. Video attachment - shows the recipe procedure

So the job at hand is to attach the video to the notification in the service extension.

Your service extension has two files, Info.plist and NotificationService.swift

You will get some boilerplate code in NotificationService. Nevertheless, let us write our code for downloading the video and setting the content for the notification.

Before that just make sure that the payload you are sending is as follows.

You can see the usual “content-available” property which signifies there are some extra keys to be taken into consideration. We have an “attachment” key which gives the download link for the video to be attached.

There is a new parameter “mutable-content” in “aps”. This signifies that the OS should initiate the service extension of the app to perform some extra processing.

In NotificationService.swift there is a NotificationService class which is a subclass of UNNotificationServiceExtension.

We will override two methods of UNNotificationServiceExtension to attach the video to the notification.

This method is called when the notification is received, as evident from the signature.

var bestAttemptContent: UNMutableNotificationContent?

bestAttemptContent will hold the notification content of the incoming notification. If you want to modify any of the parameters of this content, you have the oppurtunity to do so in this method.

We will grab the url of the video to be attached, download the video and add it as a UNNotificationAttachment to bestAttemptContent.

Note that we also store a reference to contentHandler completion handler.

var contentHandler: ((UNNotificationContent) -> Void)?

We will come to that later.

We are simply downloading the video and adding it as an attachment to bestAttemptContent.

On completion of download, we call contentHandler passed to didReceive(_:withContentHandler:)

Once the contentHandler is called the system terminates the extension and displays the notification to the user.

In case the contentHandler is not called after the allotted period of time from didReceive(_:withContentHandler:), the system terminates the extension and delivers the original notification content to the user.

In this case, what if you want to display some placeholder for the content you were trying to attach/modify ?

You simply override another method of UNNotificationServiceExtension

serviceExtensionTimeWillExpire() gives you a chance to perform any last minute processing if your notification is being terminated before you could complete all the content modification. Attaching placeholders etc, can be done here.

4. Customizing the UI for notifications

The UI delivered to the user above is the default one for a notification with video attachment. iOS 10 adds the capability to customize this UI.

Now there are a few things that don’t look possible while customizing the notification UI.

  1. Animations.
  2. It doesn’t receive touches.

If you need animations, you can use gifs instead for some basic animations you are trying to mimic.

To perform some actions which have to be initiated by the user, you can use notification actions.

But otherwise it is a simple view controller which can embed any views. For customizing the notification interface you need Notification Content Extension.

Once you create the content extension, you get a MainInterface.storyboard, NotificationViewController.swift and Info.plist

Info.plist should indicate to which category the notification interface you are trying to customize belongs.

UNNotificationExtensionCategory — Specifies the category to which our notification content extension belongs. Only if the notification being delivered is of the category specified here, will the content extension be invoked.

UNNotificationExtensionDefaultContentHidden — A default content consisting of title, subtitle, body is shown underneath the customized UI. If you are already displaying these in your custom UI, you can hide it here.

UNNotificationExtensionContentSizeRatio — Specifies the size of the notification. If 1 , width and height will be the same. You override this by using preferredContentSize or autolayout constraints.

In the MainInterface.storyboard you drag the necessary elements and make the outlet connections to NotificationViewController.

As any other view controller, NotificationViewController has a viewDidLoad() where any population can be done.

NotificationViewController should extend UNNotificationContentExtension so that it can populate content in the notification based on the content received as part of the notification.

func didReceive(UNNotification)is called when notification arrives. This is where your content should be populated.

We have title, subtitle and description labels which hold the title, subtitle and body of notification respectively.

We attached the video already using the notification service extension. So the video will be available as one of the attachments of bestAttemptContent.

I have a VideoPlayer class which encapsulates all operations to set up AVPlayer in a given view and play a video. This has been done in the above method.

To set up the video player, use the VideoPlayer class in the source code and specify the local url to the video you want to play. Then call setUpPlayerInView(view: UIView) and the play() functions.

Don’t forget to change the class of the view you are using for play, inside the class inspector to PlayerView.

If you recall, for “recipe” category we had specified two actions “like” and “comment”. For comment action, the textfield will be brought up.

To perform any functionality when user clicks on one of the actions, please use the following method of UNNotificationContentExtension.

Once the text field is brought up and dismissed, we are simple dismissing the notification.

Completion handler takes a single argument of type

UNNotificationContentExtensionResponseOption.

Here we are simply choosing to dismiss the notification. You can opt not to dismiss the notification or dismiss and forward the action to the app in case you want any in-app handling of the action to be done. You can refere more here.

That sums up our tutorial that explains the different ways to handle notifications in iOS 10. Do give me your valuable suggestions. Please feel free to go through the source code.

P.S : Houston

I used Houston to send remote notifications. Most of the 3rd party remote notification services don’t have the provision to add those extra keys they have introduced into the apns payload. You can simply download houston and modify the notification.rb file with very basic understanding of Ruby to incorporate your required keys.

References

Introduction to Notifications

Advanced Notifications

--

--