Migrating from UILocalNotification to iOS 10 [DEV GUIDE]

In iOS 10, Apple has changed their local notification management by deprecating the UILocalNotification and related classes, which were introduced in iOS 4. These classes are replaced with a new notifications framework, the UserNotification framework, that provides more and better abilities than its predecessor.

Here at Plot Projects, we strive to keep our products up to date and for that reason a migration to the NSUserNotification is imperative. However, this shift requires developers to adapt their code to the new implementations. This post intends to help some and inform others on how to do this migration, by showing how it’s done behind the scenes at Plot Projects in a generic way with Swift and Objective-C, so that you can apply to your own projects. Our framework, starting from version 2.x, supports NSUserNotification. Also, when you are using the Plot Projects library in your app and want to use rich media in your notifications, this guide is useful.

Setup:

Start by importing UserNotifications (Notifications became independent from the UIKit) at the top of your header or implementation file.

Swift:

import UserNotifications

Objective-C:

#import <UserNotifications/UserNotifications.h>

Notifications are now managed through a shared object, UNUserNotificationCenter:

Swift:

let center = UNUserNotificationCenter.current()

Objective-C:

UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];

Requesting permission

As before, you need the user’s permission for the types of notifications (badge, sound, alert and carPlay) your application will use. If for example we need to grab the user’s attention and choose to use alerts and sounds:

The old way with UILocalNotification (Deprecated):

Swift:

if(UIApplication.instancesRespondToSelector(Selector("registerUserNotificationSettings:"))) {
    UIApplication.sharedApplication().registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert | .Sound, categories: nil))
}

Objective-C:

if ([UIApplication instancesRespondToSelector:@selector(registerUserNotificationSettings:)]) {
    [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound categories:nil]];
}

In the new way with NSUserNotification, the authorization request is done using the notification center:

Swift:

let options: UNAuthorizationOptions = [.alert, .sound];
center.requestAuthorization(options: options) {
  (granted, error) in
    if !granted {
      print("Oops, no access)
    }
}

Objective-C:

UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound;
[center requestAuthorizationWithOptions:options
 completionHandler:^(BOOL granted, NSError * _Nullable error) {
  if (!granted) {
    NSLog(@"Oops, no access");
  }
}];

Creating a notification

The UILocalNotification class that was used to represent a local notification, is replaced by UNNotificationRequest which expects a notification content (UNMutableNotificationContent) and a trigger condition (that can be based on time, date or location).

A small example of usage starting with the content:

Swift:

let content = UNMutableNotificationContent()
content.title = "Hi from Plot!"
content.body = "Plot Projects says hi"
content.sound = UNNotificationSound.default()

Objective-C:

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Hi from Plot!";
content.body = @"Plot Projects says hi";
content.sound = [UNNotificationSound defaultSound];

A new feature, among others, from the framework is the support for rich push notifications (notifications with media such as audio, image or video). This type of content can be used to overcome the notification character limit with meaningful media, as well as to improve user engagement and make your notification standout from others.

In order to get rich push notifications up and running with the Plot Projects SDK we need to add each media item (UNNotificationAttachment) to the attachments array present in the UNMutableNotificationContent using the Notification Filter as follows:

Swift:

func plotFilterNotifications(filterNotifications: PlotFilterNotifications){
     for (UNNotificationRequest localNotification in filterNotifications.uiNotifications) {
         let fileURL: URL = ... //  your media item file url
 
         let customContent = UNMutableNotificationContent()
 
         let attachment = UNNotificationAttachment(identifier: "attachment", url: fileURL, options: nil)
         customContent.attachments = [attachment!]
 
         let notificationRequest = UNNotificationRequest(identifier: localNotification.identifier, content: customContent, trigger: localNotification.trigger)
  }
}

Objective-C:

-(void)plotFilterNotifications:(PlotFilterNotifications*)filterNotifications {
    for (UNNotificationRequest* localNotification in filterNotifications.uiNotifications) {
 
        NSURL *fileURL = ... //  your media item file url
        NSError *error;
 
        UNMutableNotificationContent* customContent =  [[UNMutableNotificationContent alloc] init];
 
        NSArray* attachments = [NSMutableArray array];
        UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"attachment" URL:imageURL options:nil error:&error]
        customContent.attachments = [attachments arrayByAddingObject:attachment];
 
        UNNotificationRequest* updatedNotification =  [UNNotificationRequest requestWithIdentifier:localNotification.identifier content:customContent trigger:localNotification.trigger];
    }
}

Now that we defined some content for our notification, let’s define a condition the trigger that fires the notification. You don’t have to do this when using our library. Our library manages the triggers.

A trigger can be:

  • Time based: An interval of time is set and the notification will be shown a number of seconds later. As an example, let’s define a trigger that awakes us after a 15 minute power nap:

Swift:

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 900,  repeats: false)

Objective-C:

UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:900
                                  repeats:NO];

The flag repeats allows setting a trigger as repeatable, which in our context could be used as an auto snooze for the heavy sleepers.

  • Date based: This trigger allows to schedule a date and time in which the notification is fired. For example, if we wanted to be reminded of our power nap tomorrow this could be obtained as follows:

Swift:

let triggerDate = Calendar.current.date(byAdding: .day, value: 1, to: self)!
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)

Objective-C:

NSDate *date = [NSDate dateWithTimeIntervalSinceNow:86400];
NSDateComponents *triggerDate = [[NSCalendar currentCalendar]   
              components:NSCalendarUnitYear +
              NSCalendarUnitMonth + NSCalendarUnitDay +
              NSCalendarUnitHour + NSCalendarUnitMinute +
              NSCalendarUnitSecond fromDate:date];
 
UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:triggerDate repeats:NO];

  • Location based: The notification is triggered when entering or leaving a geographic region which can be obtained by the CoreLocation module, CLRegion. Setting it up, is as simple as:

Swift:

let trigger = UNLocationNotificationTrigger(triggerWithRegion:region, repeats:false)

Objective-C:

UNLocationNotificationTrigger *locTrigger = [UNLocationNotificationTrigger triggerWithRegion:region repeats:NO];

Do note that when using this feature, the maximum number of regions that can be monitored at once is 20. Our library doesn’t have this limitation.

Scheduling a notification

The notification is now composed and the only things left are defining an identifier and adding the notification request to the notification center. The identifier is a unique reference to a certain notification, as a consequence to be able to show different notifications it’s necessary to designate different identifiers for each request, otherwise the most recent request will replace the one present in the center that shares the same identifier.

Adding the request to the center can be accomplished with:

Swift:

let notificationRequest = UNNotificationRequest(identifier: "plot_notification", content: content, trigger: trigger)
 
// Add Request to User Notification Center
center.add(notificationRequest, withCompletionHandler: { (error) in
    if let error = error {
        print("Unable to Add Notification Request")
    }
})

Objective-C:

UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:@”plot_notification” content:content trigger:trigger]
 
[center addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Unable to Add Notification Request");
    }
}];

Custom actions

The custom actions allow notifications to integrate buttons or inputs directly in the notification display which enables users with a quick way of interacting with the app without actually opening it.

It’s important to note that this feature may affect the statistics when using the Plot Projects SDK, considering that it influences the CTR (Click Through Rate) as the performed actions won’t count as a notification “Opened”. As a consequence these interactions won’t be registered nor included in the data used to generate the statistics and reports provided by Plot Projects.

The custom actions feature of the framework relies on two classes UNNotificationAction and UNNotificationCategory that represent the action itself and the category in which that action is included, accordingly. Categories serve to group actions, so that when a notification is set to a certain category those actions are automatically included.

Similarly to the rich push content, to incorporate this feature with the Plot Projects SDK, we have to resort to the Notification Filter. As an example, we’re going to define one category with one action, that can later be set to a notification in the Notification Filter

Swift:

let action = UNNotificationAction(identifier: "snooze", title: "Snooze!", options: [])
let category = UNNotificationCategory(identifier: "myCategory", actions: [action], intentIdentifiers: [], options: [])
center.setNotificationCategories([category])

Objective-C:

UNNotificationAction *action = [UNNotificationAction actionWithIdentifier: @”snooze” title:@”Snooze!” options:@[]];
 
UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier: @”myCategory” actions:@[action] intentIdentifiers:@[]  hiddenPreviewsBodyPlaceholder:@”Snooze”  options: @[]];
 
[center setNotificationCategories: [NSSet setWithObject: category]];

Now that we have a category, we can enable that action in our notifications by setting the category identifier. In our library that can be done like this:

Swift:

func plotFilterNotifications(filterNotifications: PlotFilterNotifications){
     for (UNNotificationRequest localNotification in filterNotifications.uiNotifications) {
                 ...
     content.categoryIdentifier = "myCategory"
  }
}

Objective-C:

-(void)plotFilterNotifications:(PlotFilterNotifications*)filterNotifications {
    for (UNNotificationRequest* localNotification in filterNotifications.uiNotifications) {
                ...
         content.categoryIdentifier = "myCategory"
    }
}

Notification Delegate

If your app requires notifications to be shown when the app is in the foreground, you need to implement the UNUserNotificationCenterDelegate which comes to replace the notification related methods from the UIApplicationDelegate. This protocol defines two methods, one for notifications that are delivered to a foreground app:

Swift:

userNotificationCenter(_:willPresent:withCompletionHandler:)

Objective-C:

userNotificationCenter:willPresentNotification:withCompletionHandler:

and another to let your app know which action was selected by the user for a given notification:

Swift:

userNotificationCenter(_:didReceive:withCompletionHandler:)

Objective-C:

userNotificationCenter:didReceiveNotificationResponse:withCompletionHandle:

The system defines two default identifiers, when the user taps the notification (UNNotificationDefaultActionIdentifier) or swipes to dismiss the notification (UNNotificationDismissActionIdentifier). It’s also on this last method that the handling of the custom actions is done.

Important note: The completionHandler method must be called when the handling logic is finished and the delegate object must be assigned to the UNUserNotificationCenter object before your app finishes launching.

Pending and delivered notifications

The new framework is more capable regarding pending and delivered notifications when compared to the now deprecated UILocalNotification, considering that the old framework only allowed to remove individual or all pending notifications. However, in iOS 10 it’s possible to manage both pending requests and delivered notifications that are still in the Notification Center.

The old way of getting pending notifications (Deprecated):

Swift:

UIApplication.shared.scheduleLocalNotification

Objective-C:

[UIApplication sharedApplication].scheduledLocalNotifications

The new way:

  • For pending requests:

Swift:

getPendingNotificationRequests:completionHandler:

Objective-C:

getPendingNotificationRequestsWithCompletionHandler:

  • For delivered notifications:

Swift:

getDeliveredNotificationRequests:completionHandler:

Objective-C:

getDeliveredNotificationsWithCompletionHandler:

The pending and delivered notifications are represented by the UNNotificationRequest class and the UNNotification class accordingly.

The methods for removing the notifications and requests allow to remove a particular subset

Swift:

removePendingNotificationRequests:withIdentifiers:
removeDeliveredNotifications:withIdentifiers:

Objective-C:

removePendingNotificationWithIdentifiers:
removeDeliveredNotificationsWithIdentifiers:

or the entire range of these items:

Swift and Objective-C:

removeAllPendingNotificationRequests
removeAllDeliveredNotifications

Note: The identifiers used to remove only part of the notifications and/or requests can be obtained through the UNNotificationRequest object (which can also be found in the UNNotification object).

All the methods described above are available and can be called from the shared notification center.

Further reading

Apple official documentation on notifications

Leave a Reply

Your email address will not be published. Required fields are marked *