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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Push Notifications Tutorial for iOS: Rich Push Notifications
30 mins
- Getting Started
- Setting up New App Values
- Creating an Authentication Key
- Running the App
- Testing a Push Notification
- Modifying Push Content
- Introducing Service Extensions
- Adding a Service Extension
- Exposing Files to the Extension
- Saving Files
- Downloading an Image
- Modifying the Push Content From the Server
- Updating the Title
- Adding an Image
- Creating a Custom UI
- Adding the Target
- Configuring Info.plist
- Adding the App Group
- Building the Custom UI
- Setting up NotificationViewController
- Adding Shared Files
- Customizing the UI
- Customizing the Notification
- Implementing the Favorite Action
- Implementing the Play Action
- Where to Go From Here?
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:
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 File ▸ New ▸ Target….
Filter for the Notification Service Extension and click Next:
Name the extension WendercastNotificationService. The fields should look something like this:
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:
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:
- Obtain a reference to the temp file directory.
- Using the temp file directory, create a directory URL using a unique string.
- The FileManager is responsible for creating the actual file to store the data. Call
createDirectory(at:winthIntermediateDirectories:attributes:)
to create an empty directory. - Create a file URL based on the image identifier.
- Create a
Data
object from the image. - 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:
- Ensure you can create a
URL
out of theurlString
property. - Use the
ImageDownloader
you linked to this target to attempt the download. - Ensure the resulting image is not
nil
. - 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:
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:
- Check for a value of the key podcast-guest in
userInfo
in the notification content. - If it exists, update the title of the notification content.
- 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.
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:
- Check if you have a value of podcast-image. If not, call the content handler to deliver the push and return.
- Call the convenience method to retrieve the image with the URL received from the push payload.
- When the completion block fires, check that the image is not nil; otherwise, attempt to save it to disk.
- If a URL is present, then the operation was successful; if any of these checks fail, call the content handler and return.
- Create a
UNNotificationAttachment
with the file URL. Name the identifier image to set it as the image on the final notification. - If creating the attachment succeeds, add it to the
attachments
property onbestAttemptContent
. - 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:
Pull down the notification. You’ll see it expands and uses the image to fill a large part of the screen:
Awesome! You are now able to update your notification content. Next, you’ll go further by creating custom UI around your push content.