iOS 10 has added two new notification extensions: Service and Content.

Service extensions let you change notification payloads and Content extensions gives you a way to show a custom interface for the notifications.

Service extensions:
Service extensions give you a time to process on notification payloads before notification is displayed to the user. For example, download image, GIF, Video, decrypt text to display it in the notification.

A Service extension’s entry point class
UNNotificationServiceExtension

It overrides 2 methods:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler

- (void)serviceExtensionTimeWillExpire

When the notification payload contains: mutable-content = 1 in the aps dictionary, system do not show the notification instantly to a user instead it start the service extension. Service extensions start the processing on the notification payload. Once this is completed notification is shown to the user

- (void)didReceiveNotificationRequest:

This method gets called when service extension starts. Here you can do processing required on the notification, for example: modify all of the basic properties of a notification including the title, subtitle, body, badge count,userInfo, download audio, images, GIFs, video once media is downloaded to the disk, set the media path by initializing an instance of [UNNotificationAttachment].

once all the processing is done call the completion block.

- (void)serviceExtensionTimeWillExpire;

System gives you a limited amount of time to process a notification. If this processing is not completed in the allotted time, expire methods get called. So if you are planing to download 3-4 images make sure if this is not completed within the time, create the attachment of the downloaded images and return the completion handler and cancel pending download.

You can see rich notification by using 3D touch or pulling down on the notification. Service notification displays the default UI.

What if you want to show custom UI… use Content extensions for customizing notification interface.

Content extensions:

Content extensions allows to show customized interface to the user, Remember you will still need service extensions to do all the processing required on the notification payload.

The system starts content extension when a notification with the “category” key is present in the “aps” dictionary and the key’s value should match to the value present in the content extension’s info.plist [UNNotificationExtensionCategory]. You can have multiple categories for content extension and depending on the category type UI gets updated.

Content extension’s entry point class is
UIViewController and it implements protocol

- (void)didReceiveNotification:(UNNotification *)notification;

This protocol gets called when the user expands a notification banner by 3D Touching or pulling down or by tapping the view button after swiping from right to left in Notification Center.

System appends the original notification UI at the bottom of the custom interface. If you want to hide it use:
[UNNotificationExtensionDefaultContentHidden = True] in the content extension’s info.plist

The system animates the notification to a height based on a ratio specified by the UNNotificationExtensionInitialContentSizeRatio key in your content extension’s info.plist.

If you’ve attached a media object to the notification in the service extensions you can access it in content extensions by performing one addition step. Before accessing attachment URL call [startAccessingSecurityScopedResource()] When you’re done accessing the media, you must call [stopAccessingSecurityScopedResource()]

Make sure you need to create unique bundle identifier for service and content extensions.

Service notification payload example

{"aps": {"alert": {"body": "checkout my pic", "title": "Super!"},"mutable-content": 1},"pic_url": "URL"}

Content notification payload

{"aps": {"alert": {"body": "checkout our pic", "title": "Super!"},"mutable-content": 1,"category":"myNotificationCategory"},"pic_url": "URL"}

Do remember that “mutable-content”: 1,”category”:”myNotificationCategory” these values are present within the aps dic.

We have enough information by now to start with an example so let’s start:

  1. Open xcode and create single view application named it Rich Notification.
  2. Add Push notification method in the AppDelegate this methods are required to register the app to receive notification.
  3. Then go to New-> Target and select service extensions.

service extension rich notifications mobisoft infotech

Name it ServiceExtension and save it

notification service rich notifications mobisoft infotech

Now look for the ServiceExtention folder in the project navigator and expand it you will see NotificationService.h&.m and its info.plist.

Let’s first set the provisioning profile for the service extension, as said earlier you need to have unique bundle identifier and provisioning profile for each extension

provisioning profile mobisoft infotech

Now lets write some code to download images in the service extension.

Open class NotificationService.m, and write following code in the
[didReceiveNotificationRequest]

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler
{
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    // Modify the notification content here...
    // self.bestAttemptContent.body = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.body];
    
    // check for media attachment, example here uses custom payload keys mediaUrl and mediaType
    NSDictionary *userInfo = request.content.userInfo;
    if (userInfo == nil)
    {
        [self contentComplete];
        return;
    }
    
    if ([userInfo objectForKey:@"pic_url"])
    {
        
        [self loadAttachmentForUrlString:[userInfo objectForKey:@"pic_url"]
                       completionHandler: ^(UNNotificationAttachment *attachment) {
                           self.bestAttemptContent.attachments = [NSArray arrayWithObjects:attachment, nil];
                       }];

    }
}

In this protocol, [didReceiveNotificationRequest] we access the userInfo and start downloading the image from image url using NSURL session. Once the image is downloaded UNNotificationAttachment object is initialized with local downloaded path and the attachment object is set to the notification object.

We will define custom method which will download the image in the local directory.

- (void)loadAttachmentForUrlString:(NSString *)urlString
                 completionHandler:(void (^)(UNNotificationAttachment *))completionHandler
{
    __block UNNotificationAttachment *attachment = nil;
    __block NSURL *attachmentURL = [NSURL URLWithString:urlString];

    NSString *fileExt = [@"." stringByAppendingString:[urlString pathExtension]];


    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

    NSURLSessionDownloadTask *task = [session downloadTaskWithURL:attachmentURL
                                                completionHandler: ^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
                                          if (error != nil)
                                          {
                                              NSLog(@"%@", error.localizedDescription);
                                          }
                                          else
                                          {
                                              NSFileManager *fileManager = [NSFileManager defaultManager];
                                              NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path
                                                                                        stringByAppendingString:fileExt]];
                                              [fileManager moveItemAtURL:temporaryFileLocation
                                                                   toURL:localURL
                                                                   error:&error];

                                              NSError *attachmentError = nil;
                                              attachment = [UNNotificationAttachment attachmentWithIdentifier:[attachmentURL lastPathComponent]
                                                                                                          URL:localURL
                                                                                                      options:nil
                                                                                                        error:&attachmentError];
                                              if (attachmentError)
                                              {
                                                  NSLog(@"%@", attachmentError.localizedDescription);
                                              }
                                          }
                                          completionHandler(attachment);
                                      }];

    [task resume];
}

By now, we have completed the process of downloading image and setting the image to attachment object.

Now let’s handle to return completion handler once the processing is done or when time will expire protocol gets called.

- (void)serviceExtensionTimeWillExpire
{
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
[self contentComplete];
}

- (void)contentComplete
{
[self.session invalidateAndCancel];
self.contentHandler(self.bestAttemptContent);
}

Build and run and send test notification. You can use pusher service to test this quickly, refer to the sample service extension payload example.

Now lets add Content extension,

  1. Go to New-> Target and select content extensions.
  2. Name it ContentExtension
  3. Set the bundle identifier and provisioning profile in the target for ContentExtension.

Now look for the ContentExtension folder in the project navigator and expand it you will see NotificationViewController.h&.m , info.plist and MainInterface.storyboard

As discussed earlier it is a view controller class it has [MainInterface.storyboard] attached to it you can design your custom interface here…

In the plist make sure the category name is same as the one you receive in the notification payload.

notification view controller mobisoft infotech

In the NotificationViewController.m let’s write code to access the image attachment and set it to image view.

- (void)didReceiveNotification:(UNNotification *)notification
{
    self.label.text = notification.request.content.body;
    
    NSDictionary *dict = notification.request.content.userInfo;
    
    for (UNNotificationAttachment *attachment in notification.request.content.attachments)
    {
        if ([dict objectForKey:@"pic_url"] && [attachment.identifier
                                                 isEqualToString:[[dict objectForKey:@"pic_url"] lastPathComponent]])
        {
            if ([attachment.URL startAccessingSecurityScopedResource])
            {
                NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
                
                self.picURL.image = [UIImage imageWithData:imageData];
                
                // This is done if the spread url is not downloaded then both the image view will show cover url.
                self.picURL.image = [UIImage imageWithData:imageData];
                [attachment.URL stopAccessingSecurityScopedResource];
            }
        }
    }
}

Build and run. I used Pusher services to test the notification it is simple and quick, refer to the sample content extension payload given above. Download code from here…

Conclusion:

So that is it. Isn’t it simple to work with Rich notification. Using Rich notification in the app you can improve the way the user interacts with your app. Further you can explore to provide action based rich notification to the user. So go ahead and replace your default notification with Rich notification.