Local Notifications with iOS 10

I can’t believe it is over six years since I first wrote about local notifications in iOS 4. Well in iOS 10 Apple has deprecated UILocalNotification which means it is time to get familiar with a new notifications framework.

Setup

This is a long post so let’s start easy by importing the new notifications framework:

// Swift
import UserNotifications

// Objective-C (with modules enabled)
@import UserNotifications;

You manage notifications through a shared UNUserNotificationCenter object:

// Swift
let center = UNUserNotificationCenter.current()

// Objective-C
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];

Authorization

As with the older notifications framework you need to have the user’s permission for the types of notification your App will use. Make the request early in your App life cycle such as in application:didFinishLaunchingWithOptions:. The first time your App requests authorization the system shows the user an alert, after that they can manage the permissions from settings:

Notification Authorization

There are four notification types, badge, sound, alert, carPlay you can combine as required. For example if you want both alerts and sound:

// Swift
let options: UNAuthorizationOptions = [.alert, .sound];

// Objective-C
UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound;

You make the actual authorization request using the shared notification center:

// Swift
center.requestAuthorization(options: options) {
  (granted, error) in
    if !granted {
      print("Something went wrong")
    }
}

// Objective-C
[center requestAuthorizationWithOptions:options
 completionHandler:^(BOOL granted, NSError * _Nullable error) {
  if (!granted) {
    NSLog(@"Something went wrong");
  }
}];

The framework calls the completion handler with a boolean indicating if the access was granted and an error object which will be nil if no error occurred.

Note: The user can change the notifications settings for your App at any time. You can check the allowed settings with getNotificationSettings. This calls a completion block asynchronously with a UNNotificationSettings object you can use to check the authorization status or the individual notification settings:

// Swift
center.getNotificationSettings { (settings) in
  if settings.authorizationStatus != .authorized {
    // Notifications not allowed
  }
}

// Objective-C
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
  if (settings.authorizationStatus != UNAuthorizationStatusAuthorized) {
    // Notifications not allowed
  }
}];

Creating A Notification Request

A UNNotificationRequest notification request contains content and a trigger condition:

Notification Content

The content of a notification is an instance of the UNMutableNotificationContent with the following properties set as required:

Note that when localizing the alert strings like the title it is better to use localizedUserNotificationString(forKey:arguments:) which delays loading the localization until the notification is delivered.

A quick example:

// Swift
let content = UNMutableNotificationContent()
content.title = "Don't forget"
content.body = "Buy some milk"
content.sound = UNNotificationSound.default()

// Objective-C
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.title = @"Don't forget";
content.body = @"Buy some milk";
content.sound = [UNNotificationSound defaultSound];

Notification Trigger

Trigger a notification based on time, calendar or location. The trigger can be repeating:

Scheduling

With both the content and trigger ready we create a new notification request and add it to the notification center. Each notification request requires a string identifier for future reference:

// Swift
let identifier = "UYLLocalNotification"
let request = UNNotificationRequest(identifier: identifier,
              content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
  if let error = error {
    // Something went wrong
  }
})

// Objective-C
NSString *identifier = @"UYLLocalNotification";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
                                  content:content trigger:trigger]

[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Something went wrong: %@",error);
    }
}];

Note: Calling addNotificationRequest with the same identifier string will replace the existing notification. If you want to schedule multiple requests use a different identifier each time.

Custom Actions

To include custom actions for the user in a notification you first need to create and register notification categories. A category defines a type of notification that can have one or more actions. As a practical example, let’s create a category with two actions. There are three key steps:

To include this action in our notifications we need to set the category in the notification content:

// Swift
content.category = "UYLReminderCategory"

// Objective-C
content.category = @"UYLReminderCategory";

The custom action now appears as part of the user notification:

Notification Action

You can display up to four actions but depending on screen space the user may not see them all. We will look in the next section at how to respond when the user selects the custom action.

The Notification Delegate

If you want to respond to actionable notifications or receive notifications while your app is in the foreground you need to implement the UNUserNotificationCenterDelegate. This protocol defines two optional methods:

In both cases you must call the completion handler once you finish.

You could use the app delegate but I prefer to create a separate class. Here is what a minimal notification delegate could like:

class UYLNotificationDelegate: NSObject, UNUserNotificationCenterDelegate {

  func userNotificationCenter(_ center: UNUserNotificationCenter,
       willPresent notification: UNNotification, 
      withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    // Play sound and show alert to the user
    completionHandler([.alert,.sound])
  }

  func userNotificationCenter(_ center: UNUserNotificationCenter,
       didReceive response: UNNotificationResponse,
       withCompletionHandler completionHandler: @escaping () -> Void) {

    // Determine the user action
    switch response.actionIdentifier {
    case UNNotificationDismissActionIdentifier:
      print("Dismiss Action")
    case UNNotificationDefaultActionIdentifier:
      print("Default")
    case "Snooze":
      print("Snooze")
    case "Delete":
      print("Delete")  
    default:
      print("Unknown action")
    }
    completionHandler()
  }
}

It is important to set the delegate before your app finishes launching. For example, in the application delegate method didFinishLaunchingWithOptions:

// Do NOT forget to retain your delegate somewhere
let notificationDelegate = UYLNotificationDelegate()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  let center = UNUserNotificationCenter.current()
  center.delegate = notificationDelegate

  // ...
  return true
}

Managing Pending and Delivered

The old UILocalNotification framework always allowed you to remove individual or all pending notifications. The new UserNotifications framework greatly extends that by allowing you to manage both pending requests and delivered notifications that are still displayed in Notification Center. There are three similar methods for each:

Both methods return an array of objects in the completion handler. For pending requests you get an array of UNNotificationRequest objects. For delivered notifications you get an array of UNNotification objects which contain the original UNNotificationRequest and the delivery date.

Removes pending requests or delivered notifications. You pass these methods an array of the string identifiers used to schedule the notifications. If you first retrieve the array of pending requests or delivered notifications the identifier is a property of the UNNotificationRequest object.

As the name suggests these methods remove all pending requests or delivered notifications.

You call these methods on the shared notification center. For example to remove all pending notification requests:

// Swift
center.removeAllPendingNotificationRequests()

// Objective-C
[center removeAllPendingNotificationRequests];

Further Reading

To read more and learn about some of the new features that I have not mentioned such as notification service extensions and notification content extensions take a look at the following WWDC sessions:

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

Unsubscribe at any time.
Archives Categories
comments powered by Disqus