We're finishing up our extensive look into the new Notifications improvements in iOS 10 today with Notification Service Extensions. These fill out the notifications functionality by allowing us to intercept remote notifications as they are received on the device, take some action(s), and then modify the notification before it's shown to the user. Let's get started!

We'll begin by looking at the broad concept going on here. Until iOS 10, Remote Notifications were presented to the user as soon as they arrived, without any interaction from their app.

In iOS 10, Apple has provided a mechanism for us to actually "intercept" Remote Notifications when they arrive, do some work, modify the notification's content, then send it along to be displayed to the user.

The whole process now goes like this:

This new step opens up a ton of possibilites and gives us a chance to do things like download media, or perform some other short work to enrich the notification before the user sees it.

Let's try this out.

First, we'll add another new target to our app, and choose a Notification Service Extension:

In the new group that was created we'll find one file, a subclass of UNNotificationServiceExtension.

Notification Service Extensions run in the background, and never show any kind of interface themselves. Instead, they override one function that's called whenever a notification is received.

We're passed in a notification request and a completion closure.

The request is the same UNNotificationRequest type we first learned about in Bite #258, only with its trigger property set to a UNPushNotificationTrigger, neat!

We'll perform whatever work we need, grab the content from the the notification request's, modify its properties, and then send it along in the completion closure:

class NotificationService: UNNotificationServiceExtension {
  override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    guard let asteroidID = request.content.userInfo["asteroid-id"] as? String else { return }
    guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else { return }

    AsteroidService.details(asteroidID: asteroidID) {
      mutableContent.body
        .append(" Estimated Mass: \($0.massInTons) tons")

      contentHandler(mutableContent)
    }
  }
}

Here we take advantage of the custom "userInfo" dictionary our server adds to the push notification payload. In this case, we're grabbing an asteroidID from it. We also grab a mutable instance of the notification's content so we can modify it later.

Then we load the details for the asteroid, and append a new piece of information to the notification's body, the estimated mass.

Finally, we send the modified content along in the contentHandler.

We don't need it here, but there's one more function we can override, and it's all about expiration.

Unlike some other more generous extension points, Notification Service Extensions are given an extremely short window to perform work and complete by the system.

We can override:

override func serviceExtensionTimeWillExpire() {

}

This will tell us when the system is about to forcibly stop our extension, if this happens, the system will display the notification using whatever push notification payload our server sent through, with no modifications.

In our example above, we lean on this fallback behavior, so we didn't need to override this function.

Finally, we'll push an example notification through using Knuff, (covered in Bite #177), and try this all out:

Success!