Notifications have gotten more than a visual refresh in iOS 10. As part of the new UserNotifications framework, Apple has given us the ability to make notifications that contain images, sound, video, or even custom content generated on the device. Much of this capability comes via three new features showcased at WWDC: media attachments, notification service extensions, and notification content extensions.
Media Attachments
A media attachment is an object we can attach to a notification that contains the URL of a media file. Supported media file types include common audio, video, and image formats. There is even built-in support for animated gifs–a surprising fact considering the lack of such support in classes like UIImageView. iOS 10 presents media attachments in a way that will look familiar if you’ve ever received a notification for a photo message from Apple’s Messages app.
Like a Messages alert, a notification containing an image or video attachment shows a thumbnail version of the attachment. This is a nice preview, but the really special part happens when you 3D Touch the notification. New in iOS 10, the notification now expands and displays a full-size version of the attachment with animation, action buttons, and playback controls enabled automatically.
Why They Are Awesome
Media attachments open up new opportunities for user engagement with our apps. Before, we were limited to just a couple lines of text; we had to rely on the user opening the app to do anything more. However, in keeping with Apple’s push for “preview” style interactions using 3D Touch, we can now engage users with rich content without them having to open the app. This is a win-win for app makers and users. App makers get to deliver more compelling content in their notifications, and users get more choice in their level of notification interaction.
How They Work
Media attachments work slightly different between local notifications and remote notifications. In the case of a local notification, its media attachment must contain the URL of a file on disk at the time that an app creates the notification. The file is copied when the notification is scheduled, and that file is delivered along with the notification. No extra effort is required.
For a remote notification, a remote notification service delivers information about the media attachment as part of the APNS notification payload. This includes the attachment URL, which, importantly, does not have to be the URL of a file already on the device. However, iOS 10 will not automatically deliver an attachment with a notification if that attachment is not stored locally. Apple’s solution to this problem is something called the Notification Service Extension.
Notification Service Extensions
A Notification Service Extension is something we can build into an app that may download or change the content of that app’s remote notifications without even opening the app. At 4KB in size, a remote notification’s payload is too small to deliver a media attachment file itself. Instead, we define an attachment URL in the remote notification payload. Once a device receives the remote notification payload for our app, the app’s service extension gets the chance to download the file at the URL and attach it to the notification before the device displays the notification to the user.
Why They Are Awesome
My first thought when looking at this feature was, why do we need this? Why doesn’t iOS just download the attachment automatically and add it to the notification? However, looking at some of the code generated when adding this extension in Xcode reveals Apple’s intentions. Below is one of the functions we need to fill in when implementing a Notification Service Extension.
1
2
3
4
5
6
7
|
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
|
Network calls are inherently unreliable, and this method is one way Apple has given us to guard against poor user experience as a consequence. In the case where our attachment takes too long to download, the OS executes this serviceExtensionTimeWillExpire() callback, allowing us to fail gracefully and deliver a notification with “best attempt” content. What that content should be will depend on the app, but the Notification Service Extension provides us the ability deliver it if necessary.
How They Work
On the notification server side, sending a notification that will be processed by a service extension is as simple as setting two fields in the APNS payload.
1
2
3
4
5
6
7
|
{
aps: {
alert: {...}
mutable-content: 1
}
my-attachment: "https://foo.bar/baz.jpg"
}
|
The mutable-content fields tells iOS that the notification has content that can be changed by a service extension before delivery, and the my-attachment field contains the URL for the content to be downloaded.
To make an app extension that will handle that notification, we just add a notification service extension as a new target in the app’s Xcode project. Xcode will generate a file containing a subclass of UNNotificationServiceExtension with two functions to be overridden.
1
2
3
4
5
6
7
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler:(UNNotificationContent) -> Void) {
// ...
}
// ...
override func serviceExtensionTimeWillExpire() {
// ...
}
|