Local notifications provide a core functionality in many apps, and in Swift they are easy to add to whatever you’re building. With iOS10’s UserNotifications you can even display GIFs in notifications.
Let’s build a quick application to display a local UserNotification
containing a SEGA-related GIF and a prompt for the user to continue receiving notifications.
Setting up the project
Get started by grabbing this example project off of Github. It takes care of all the setup so we can focus on the important stuff. Open your terminal, navigate to where you want this project to live, clone this repository and check into the tutorial branch:
git clone https://github.com/sagnew/sega-gifs.git
cd sega-gifs
git checkout tutorial
This app has several dependencies:
- Alamofire for sending HTTP requests to the Giphy API.
- SwiftyJSON for handling with the JSON response.
- PromiseKit for writing cleaner asynchronous code. If you’re unfamiliar with Promisekit, check out this other tutorial I wrote.
We’ll use CocoaPods to install these dependencies. First, install CocoaPods if you don’t have it:
sudo gem install cocoapods
Now install the dependencies by running the following command in the directory where our Podfile
is:
pod install
CocoaPods links the dependencies of our project together by creating a new Xcode Workspace. To make sure XCode loads those dependencies open the workspace using the PlayingWithUserNotifications.xcworkspace
file instead of the Xcode project file:
open PlayingWithUserNotifications.xcworkspace
Feel free to build the project to make sure everything compiles correctly before kicking off this adventure.
Displaying Notifications
Let’s hop into some code. First we’ll need to import UserNotifications
at the top of AppDelegate.swift
:
import UserNotifications
Before being able to send notifications, our app will have to request permission from the user in AppDelegate.swift
. Replace the code in application(_:didFinishLaunchingWithOptions:)
with the following so that the app will request permission to send notifications when it launches:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Request Permission
UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .alert, .badge]) {
granted, error in
if granted {
print("Approval granted to send notifications")
} else {
print(error)
}
}
UNUserNotificationCenter.current().delegate = self
return true
}
Note that before returning, we are setting the UNUserNotificationCenter
delegate to self
.
Before we’re done in AppDelegate.swift
, add an extension at the bottom of the file for handling the notification center logic:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.alert)
}
}
Now jump over to ViewController.swift
so we can begin writing the code to actually send a notification. Add an identifier for our notification in the ViewController
class:
let notificationIdentifier = "myNotification"
Next add a method in ViewController
to schedule the notification itself:
func scheduleNotification(inSeconds: TimeInterval, completion: @escaping (Bool) -> ()) {
// Create Notification content
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Check this out"
notificationContent.subtitle = "It's a notification"
notificationContent.body = "WHOA COOL"
// Create Notification trigger
// Note that 60 seconds is the smallest repeating interval.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: inSeconds, repeats: false)
// Create a notification request with the above components
let request = UNNotificationRequest(identifier: notificationIdentifier, content: notificationContent, trigger: trigger)
// Add this notification to the UserNotificationCenter
UNUserNotificationCenter.current().add(request, withCompletionHandler: { error in
if error != nil {
print("\(error)")
completion(false)
} else {
completion(true)
}
})
}
This function might seem a little long, so let me explain what’s going on:
- We’re creating a
UNMutableNotificationContent
object and setting the notification content. - Next we’re creating a
UNTimeIntervalNotificationTrigger
which is a trigger for the notification. In this case, we’re going to be scheduling a notification for the number of seconds that gets passed to this wrapper function.
- After all of this, we’re making a
UNNotificationRequest
object for our notification identifier, content and trigger. - Finally we are adding the notification request to the
UNUserNotificationCenter
.
Once this is taken care of, let’s call this new scheduleNotification
method whenever the button in our app is clicked. Add the following code to the notificationButtonTapped
method:
@IBAction func notificationButtonTapped(_ sender: Any) {
self.scheduleNotification(inSeconds: 5, completion: { success in
if success {
print("Successfully scheduled notification")
} else {
print("Error scheduling notification")
}
})
}
You should be ready to run the app, press the button and receive a notification.
Displaying GIFs in Notifications
That was cool, but let’s spice this notification up a bit with some 16-bit awesomeness.
To add GIFs to our notifications, we will use the Giphy API. Let’s make a class to wrap around and deal with this API. Create a file called GiphyManager.swift
and in it add the following code:
import UIKit
import Alamofire
import SwiftyJSON
import PromiseKit
class GiphyManager: NSObject {
let giphyBaseURL = "https://api.giphy.com/v1/gifs/search"
let apiKey: String
let imageLimit: UInt32
override init() {
self.apiKey = "dc6zaTOxFJmzC"
self.imageLimit = 50
super.init()
}
init(apiKey: String, imageLimit: UInt32) {
self.apiKey = apiKey
self.imageLimit = imageLimit
super.init()
}
func fetchRandomGifUrl(forSearchQuery query: String) -> Promise<String> {
return Promise { fulfill, reject in
Alamofire.request(self.giphyBaseURL, parameters: ["api_key": self.apiKey, "q": query, "limit": "(self.imageLimit)"])
.responseJSON { response in
if let result = response.result.value {
let json = JSON(result)
let randomNum:Int = Int(arc4random_uniform(self.imageLimit))
if let imageUrlString = json["data"][randomNum]["images"]["downsized"]["url"].string {
print(imageUrlString)
fulfill(imageUrlString)
} else {
reject(response.error!)
}
}
}
}
}
}
This is a wrapper around the Giphy REST API and is very similar to what I did in my PromiseKit tutorial. This class provides a method that grabs a URL to a random GIF on Giphy with the given query.
Let’s clean up the code we wrote for scheduling the notification. First, start by taking out most of the extra code written in ViewController.swift
and move it into a new file.
Create a new Swift file called NotificationManager.swift
and add the following code:
import UIKit
import UserNotifications
import Alamofire
import SwiftyJSON
import PromiseKit
class NotificationManager: NSObject {
static let sharedInstance = NotificationManager()
let notificationIdentifier = "myNotification"
func createNotification() {
let giphy = GiphyManager()
giphy.fetchRandomGifUrl(forSearchQuery: "SEGA Genesis").then { imageUrlString in
NotificationManager.sharedInstance.handleAttachmentImage(forImageUrl: imageUrlString)
}.then { attachmentUrl in
NotificationManager.sharedInstance.scheduleNotification(inSeconds: 5, attachmentUrl: attachmentUrl) { success in
print(success)
}
}.catch { error in
print(error)
}
}
func handleAttachmentImage(forImageUrl imageUrlString: String) -> Promise<URL> {
return Promise { fulfill, reject in
Alamofire.request(imageUrlString).responseData { response in
if let data = response.result.value {
print("image downloaded: (data)")
let fm = FileManager.default
let docsUrl = try! fm.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileUrl = docsUrl.appendingPathComponent("img.gif")
do {
try data.write(to: fileUrl)
fulfill(fileUrl)
} catch {
reject(error)
}
}
}
}
}
func scheduleNotification(inSeconds: TimeInterval, attachmentUrl: URL, completion: @escaping (Bool) -> ()) {
// Create an attachment for the notification
var attachment: UNNotificationAttachment
attachment = try! UNNotificationAttachment(identifier: notificationIdentifier, url: attachmentUrl, options: .none)
// Create Notification content
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Genesis does what Ninten-DON'T"
notificationContent.subtitle = "Blast Processing!"
notificationContent.body = "Did you know the SEGA Genesis' Yamaha YM2612 sound chip had six FM channels?"
notificationContent.attachments = [attachment]
// Create Notification trigger
// Note that 60 seconds is the smallest repeating interval.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: inSeconds, repeats: false)
// Create a notification request with the above components
let request = UNNotificationRequest(identifier: notificationIdentifier, content: notificationContent, trigger: trigger)
// Add this notification to the UserNotificationCenter
UNUserNotificationCenter.current().add(request, withCompletionHandler: { error in
if error != nil {
print("(error)")
completion(false)
} else {
completion(true)
}
})
}
}
Here we have a singleton object to handle all of the notification logic in our app.
In this class, we have a new scheduleNotification
method that is mostly the same as the one we wrote before in ViewController.swift
, but with logic to handle attachments.
There’s also a method for handling the attachment images for our notifications. This function returns a Promise and takes the Giphy URL. It then downloads the image and saves it to a file. That file URL gets passed to the next function in the chain.
The createNotification
method is what we will call from ViewController.swift
whenever the button in the app is pressed. This will kick off the call chain that will grab an image from Giphy, prepare it to be placed as an attachment in a notification, and then schedule that notification to be sent.
Now head back over to ViewController.swift
and remove the code we wrote before: both the scheduleNotification
function and the let notificationIdentifier = "myNotification"
line at the top of the class.
Replace the code in notificationButtonTapped
with the following to create a notification:
@IBAction func notificationButtonTapped(_ sender: Any) {
NotificationManager.sharedInstance.createNotification()
}
Run the app and your notification should contain a sick SEGA GIF.
Adding Input Options to your Notifications
Let’s make it so that we can receive input from our notification and allow the user to select whether or not they want more notifications.
We need to add a category identifier to the notification for confirmation options. In NotificationManager.swift
add the highlighted line to the scheduleNotification
function:
func scheduleNotification(inSeconds: TimeInterval, attachmentUrl: URL, completion: @escaping (Bool) -> ()) {
// Create an attachment for the notification
var attachment: UNNotificationAttachment
attachment = try! UNNotificationAttachment(identifier: notificationIdentifier, url: attachmentUrl, options: .none)
// Create Notification content
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Genesis does what Ninten-DON'T"
notificationContent.subtitle = "Blast Processing!"
notificationContent.body = "Did you know the SEGA Genesis' Yamaha YM2612 sound chip had six FM channels?"
notificationContent.attachments = [attachment]
// Add a category
notificationContent.categoryIdentifier = "moreNotificationsOptions"
// Create Notification trigger
// Note that 60 seconds is the smallest repeating interval.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: inSeconds, repeats: false)
// Create a notification request with the above components
let request = UNNotificationRequest(identifier: notificationIdentifier, content: notificationContent, trigger: trigger)
// Add this notification to the UserNotificationCenter
UNUserNotificationCenter.current().add(request, withCompletionHandler: { error in
if error != nil {
print("\(error)")
completion(false)
} else {
completion(true)
}
})
}
Now head over to AppDelegate.swift
to create the actions that will be placed in this notification. Replace the code in application(_:didFinishLaunchingWithOptions:)
with the following:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Request Permission
UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .alert, .badge]) {
granted, error in
if granted {
print("Approval granted to send notifications")
} else {
print("\(error)")
}
}
let yesAction = UNNotificationAction(identifier: "yes", title: "Yes", options: [])
let noAction = UNNotificationAction(identifier: "no", title: "No", options: [])
let category = UNNotificationCategory(identifier: "moreNotificationsOptions", actions: [yesAction, noAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
UNUserNotificationCenter.current().delegate = self
return true
}
All that’s left is to update the extension we previously made in AppDelegate.swift
and add a method to actually define what happens when the user clicks on the “Yes” option. In this case we are just calling createNotification
again. Replace that extension with the following:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.alert)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if response.actionIdentifier == "yes" {
NotificationManager.sharedInstance.createNotification()
completionHandler()
}
}
}
Now run the app again and prepare for some repeating notifications
Notify the World
Now you can send local UserNotifications that contain GIFs and even receive input! That’s only the beginning when it comes to notifications. You can send notifications from a remote server if you want to and even use Twilio Notify to send push notifications.
Feel free to reach out if you have any questions or comments or just want to show off the cool stuff you’ve built.
- Email: sagnew@twilio.com
- Twitter: @Sagnewshreds
- Github: Sagnew
- Twitch (streaming live code): Sagnewshreds