Notification Extension

🚧

Xcode version < 12

If xcode version is lower than 12,
See this page.
However, it is legacy and is not recommended.

🚧

When an error such as 'ITMS-90680' or 'ITMS-90685' occurs in the environment using SPM and Extension

In an environment where SPM and Extension are used, errors such as'ITMS-90680' or'ITMS-90685' may occur during the App Store upload process.

Please refer to the link for the solution

To serve better user experience for our push notification, you can optionally add Notification service extension in your application to display manager's avatar on push notification.

Extension Setup

In the Xcode menu bar, go to File > New > Target... and select the Notification Service Extension template from the menu that appears:

Once your extension has been created, you will see two files (or three if you're using Objective-C) within the extension folder in the Xcode Project navigator:

  • NotificationService.swift, which will contain all the code and logic for your extension.
  • Info.plist, which contains configuration details for your extension.

The Info.plist file contains all the information required for your extension, so the only file you should need to change is the NotificationService.swift file.

Extension Lifecycle

Once you have configured your app with a notification service extension, the following process will take place for each notification:

  • App receives notification.
  • System creates an instance of your extension class and launches it in the background.
  • Your extension performs content edits and/or downloads some content.
  • If your extension takes too long to perform its work, it will be notified and immediately terminated.
  • Notification is displayed to the user.

As you can see, when using a notification service extension, you only have a limited amount of time to perform the necessary work.

Additinal Code On Podfile

Before add Extension Code, you need to add ChannelIO in Podfile

target 'your-notification-extention-target-name' do
  pod 'ChannelIO'
end

Extension Code

In your NotificationService implementation file (NotificationService.m in objective-c, NotificationService.swift in Swift), you literally need to copy the code below based on your base language.

import UserNotifications
import ChannelIOFront

class NotificationService: UNNotificationServiceExtension {
  var contentHandler: ((UNNotificationContent) -> Void)?
  var content: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    self.content        = (request.content.mutableCopy() as? UNMutableNotificationContent)
      
    // this line need to combine sms feature during app terminated
    ChannelIO.receivePushNotification(request.content.userInfo)
      
    if let bca = self.content {
      func save(_ identifier: String, data: Data, options: [AnyHashable: Any]?) -> UNNotificationAttachment? {
        let directory = URL(fileURLWithPath: NSTemporaryDirectory())
          .appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString, isDirectory: true)
        
        do {
          try FileManager.default.createDirectory(
            at: directory, withIntermediateDirectories: true, attributes: nil
          )
          let fileURL = directory.appendingPathComponent(identifier)
          try data.write(to: fileURL, options: [])
          return try UNNotificationAttachment.init(
            identifier: identifier, url: fileURL, options: options
          )
        } catch {
          
        }
        
        return nil
      }
      
      func exitGracefully(_ reason: String = "") {
        let bca = request.content.mutableCopy() as? UNMutableNotificationContent
        contentHandler(bca!)
      }
      
      guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
        return exitGracefully()
      }
      
      let userInfo : [AnyHashable: Any] = request.content.userInfo
      
      guard let attachmentURL = (userInfo["thumbUrl"] ?? userInfo["avatarUrl"]) as? String else {
          return exitGracefully()
        }
      
      guard let imageData = try? Data(contentsOf: URL(string: attachmentURL)!) else {
        return exitGracefully()
      }
      
      guard let attachment = save("\(attachmentURL.hashValue).png", data: imageData, options: nil) else {
        return exitGracefully()
      }
      
      content.attachments = [attachment]
      contentHandler(content.copy() as! UNNotificationContent)
    }
  }
  
  override func serviceExtensionTimeWillExpire() {
    if let contentHandler = contentHandler, let bac = self.content {
        contentHandler(bac)
    }
  }
}
@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    if (self.bestAttemptContent != nil) {
        UNMutableNotificationContent *content = request.content.mutableCopy;
        NSDictionary *userInfo = request.content.userInfo;
      
        NSString *attachmentURL = userInfo[@"avatarUrl"];
        if (attachmentURL == nil) {
            return [self exitGracefully:request withContentHandler:contentHandler];
        }
      
        NSURL *url = [[NSURL alloc] initWithString:attachmentURL];
        NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
      
        if (imageData == nil) {
            return [self exitGracefully:request withContentHandler:contentHandler];
        }
      
        UNNotificationAttachment *attachment = [self save:[NSString stringWithFormat:@"%ld.png", attachmentURL.hash] data:imageData options:nil];
        if (attachmentURL == nil) {
            return [self exitGracefully:request withContentHandler:contentHandler];
        }
      
        content.attachments = @[attachment];
        self.contentHandler(content.copy);
    }
}

- (void)exitGracefully:(UNNotificationRequest* )request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    UNMutableNotificationContent *bca = request.content.mutableCopy;
    contentHandler(bca);
}

- (UNNotificationAttachment *)save:(NSString *)identifier data:(NSData *)data options:(NSDictionary *)options {
    NSURL *directory = [[[NSURL alloc] initFileURLWithPath:NSTemporaryDirectory() isDirectory:YES]
                        URLByAppendingPathComponent:NSProcessInfo.processInfo.globallyUniqueString];
  
    NSError *error = [[NSError alloc] init];
    [[NSFileManager defaultManager] createDirectoryAtURL:directory withIntermediateDirectories:YES attributes:nil error:&error];
  
    if (error != nil) {
        return nil;
    }
  
    NSURL *fileURL = [directory URLByAppendingPathComponent:identifier];
    [data writeToURL:fileURL atomically:YES];
    UNNotificationAttachment *ret = [UNNotificationAttachment attachmentWithIdentifier:identifier URL:fileURL options:options error:&error];
    if (error != nil) {
        return nil;
    }
    return ret;
}

- (void)serviceExtensionTimeWillExpire {
    self.contentHandler(self.bestAttemptContent);
}

@end

That's it :+1:. Now when you send a message to your customers via Channel, they will see your avatar on push notification.