Push Notifications Tutorial for iOS: Rich Push Notifications

Learn how to modify and enhance push notifications before they are presented to the user, how to create custom UI around your push content, and more! By Mark Struzinski.

4.6 (14) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Modifying Push Content

Apple has created a way to modify push content prior to delivery with service extensions. Service extensions allow you to intercept the push content coming in from the APNS, modify it and then deliver the modified payload to the user.

The service extension sits between the APNS server and the final content of the push notification:

Service extension diagram

Introducing Service Extensions

A service extension gets a limited execution time to perform some logic on the incoming push payload. Some of the things you can do to modify and augment the push payload are:

  • Update the title, subtitle or body of the push.
  • Add a media attachment to the push.

Adding a Service Extension

Go back to the Wendercast project and create a new target by clicking FileNewTarget….

Filter for the Notification Service Extension and click Next:

Adding notification service extension target

Name the extension WendercastNotificationService. The fields should look something like this:

Service extension configuration

Once you’ve verified the field inputs, click Finish. Do not activate the new scheme if prompted.

With that, you’ve added a notification service extension into the project and you’re ready to intercept some push notifications. :]

Exposing Files to the Extension

You’ll begin by exposing some of the helper classes that were included in the project to the new services extension you created. In the Network directory, you’ll find two files: ImageDownloader.swift and NetworkError.swift.

In the File inspector, add a check to the WendercastNotificationService target so they can be used inside the services extension:

Target membership setup

Saving Files

In the WendercastNotificationService group, open NotificationService.swift and import UIKit at the top of the file.

import UIKit

At the bottom of NotificationService, add this convenient method to save an image to disk:

private func saveImageAttachment(
  image: UIImage,
  forIdentifier identifier: String
) -> URL? {
  // 1
  let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
  // 2
  let directoryPath = tempDirectory.appendingPathComponent(
    ProcessInfo.processInfo.globallyUniqueString,
    isDirectory: true)

  do {
    // 3
    try FileManager.default.createDirectory(
      at: directoryPath,
      withIntermediateDirectories: true,
      attributes: nil)

    // 4
    let fileURL = directoryPath.appendingPathComponent(identifier)

    // 5
    guard let imageData = image.pngData() else {
      return nil
    }

    // 6
    try imageData.write(to: fileURL)
      return fileURL
    } catch {
      return nil
  }
}

Here’s what you’ve done:

  1. Obtain a reference to the temp file directory.
  2. Using the temp file directory, create a directory URL using a unique string.
  3. The FileManager is responsible for creating the actual file to store the data. Call createDirectory(at:winthIntermediateDirectories:attributes:) to create an empty directory.
  4. Create a file URL based on the image identifier.
  5. Create a Data object from the image.
  6. Attempt to write the file to disk.

Now that you’ve created a way to store the image, you’ll turn your attention to downloading the actual image.

Downloading an Image

Add another method to download an image from a URL:

private func getMediaAttachment(
  for urlString: String,
  completion: @escaping (UIImage?) -> Void
) {
  // 1
  guard let url = URL(string: urlString) else {
    completion(nil)
    return
  }

  // 2
  ImageDownloader.shared.downloadImage(forURL: url) { result in
    // 3
    guard let image = try? result.get() else {
      completion(nil)
      return
    }

    // 4
    completion(image)
  }
}

What it does is pretty simple:

  1. Ensure you can create a URL out of the urlString property.
  2. Use the ImageDownloader you linked to this target to attempt the download.
  3. Ensure the resulting image is not nil.
  4. Call the completion block, passing the UIImage result.

Modifying the Push Content From the Server

You’ll need to add a few extra values to the push payload you are sending to the device. Go into the Push Notification Tester app and replace the body with this payload:

{
  "aps": {
    "alert": {
      "title": "New Podcast Available",
      "subtitle": "Antonio Leiva – Clean Architecture",
      "body": "This episode we talk about Clean Architecture with Antonio Leiva."
    },
    "mutable-content": 1
  },
  "podcast-image": "https://koenig-media.raywenderlich.com/uploads/2016/11/Logo-250x250.png",
  "podcast-guest": "Antonio Leiva"
}

This payload gives your push notification a title, a subtitle and a body.

Notice mutable-content has a value of 1. This value tells iOS that the content is updatable, causing it to invoke the service extension before delivering it to the user.

There are two custom keys added: podcast-image and podcast-guest. You’ll use the values associated with these keys to update the push content before displaying the notification to the user.

Send the push with the above content now. You’ll see an updated push notification with the title, subtitle and description added. It looks like this:

Push with modified content

Updating the Title

The power of the notification service extension comes from its ability to intercept pushes. You’ll get a taste of that in this section. In WendercastNotificationService, open NotificationService.swift and locate didReceive(_:withContentHandler:). This function is called when a push notification comes in, and allows you to perform some adjustments to the content you’ll be displaying to the user.

Replace the if let block with following:

if let bestAttemptContent = bestAttemptContent {
  // 1
  if let author = bestAttemptContent.userInfo["podcast-guest"] as? String {
    // 2
    bestAttemptContent.title = "New Podcast: \(author)"
  }

  // 3
  contentHandler(bestAttemptContent)
}

Here’s what you’ve done:

  1. Check for a value of the key podcast-guest in userInfo in the notification content.
  2. If it exists, update the title of the notification content.
  3. Call the completion handler to deliver the push. If podcast-author‘s value is not present, the push displays the original title.

Build and run. Then send the app to the background. Now send a push from the Push Notifications Tester app. You should see a push notification with an updated title that now contains the value from the podcast-author entry.

Push with updated title

Adding an Image

Next, you’ll use the push payload to download an image representing the podcast episode.

Replace the line contentHandler(bestAttemptContent) with the following:

// 1
guard let imageURLString =
  bestAttemptContent.userInfo["podcast-image"] as? String else {
  contentHandler(bestAttemptContent)
  return
}

// 2
getMediaAttachment(for: imageURLString) { [weak self] image in
  // 3
  guard 
    let self = self,
    let image = image,
    let fileURL = self.saveImageAttachment(
      image: image,
      forIdentifier: "attachment.png") 
    // 4
    else {
      contentHandler(bestAttemptContent)
      return
  }

  // 5
  let imageAttachment = try? UNNotificationAttachment(
    identifier: "image",
    url: fileURL,
    options: nil)

  // 6
  if let imageAttachment = imageAttachment {
    bestAttemptContent.attachments = [imageAttachment]
  }

  // 7
  contentHandler(bestAttemptContent)
}

Here’s what’s happening above:

  1. Check if you have a value of podcast-image. If not, call the content handler to deliver the push and return.
  2. Call the convenience method to retrieve the image with the URL received from the push payload.
  3. When the completion block fires, check that the image is not nil; otherwise, attempt to save it to disk.
  4. If a URL is present, then the operation was successful; if any of these checks fail, call the content handler and return.
  5. Create a UNNotificationAttachment with the file URL. Name the identifier image to set it as the image on the final notification.
  6. If creating the attachment succeeds, add it to the attachments property on bestAttemptContent.
  7. Call the content handler to deliver the push notification.

Build and run. Send the app to the background.

Now, send another push from the Push Notifications Tester app with the same payload. You should see the push come in with an image in the top right corner:

Modified push with image

Pull down the notification. You’ll see it expands and uses the image to fill a large part of the screen:

Push with expanded image

Awesome! You are now able to update your notification content. Next, you’ll go further by creating custom UI around your push content.