{"_id":"5b720760c44b7600034b79f0","user":"55d29988486de50d00327118","__v":0,"parentDoc":null,"project":"55e67aaa9cc7c62b00c4a1ea","category":{"_id":"5b720760c44b7600034b79ac","project":"55e67aaa9cc7c62b00c4a1ea","__v":0,"version":"5b720760c44b7600034b7a08","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2017-02-08T21:31:11.878Z","from_sync":false,"order":6,"slug":"advanced-techniques","title":"Advanced Techniques"},"version":{"_id":"5b720760c44b7600034b7a08","project":"55e67aaa9cc7c62b00c4a1ea","__v":0,"forked_from":"5b1f2cbdfd653400031d8d9f","createdAt":"2015-09-02T04:27:23.612Z","releaseDate":"2015-09-02T04:27:23.612Z","categories":["5b720760c44b7600034b79a7","5b720760c44b7600034b79a8","5b720760c44b7600034b79a9","5b720760c44b7600034b79aa","5b720760c44b7600034b79ab","561c61b4ad272c0d00a892df","586c014c0abf1d0f000d04d4","58991d2ad207df0f0002186b","5b720760c44b7600034b79ac","5b720760c44b7600034b79ad","5af0fe494ca2730003cbc98a","5af0fe55ec80af0003804ca2"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"API V6","version_clean":"1.4.0","version":"1.4"},"githubsync":"","updates":[],"next":{"pages":[],"description":""},"createdAt":"2016-11-03T03:54:50.295Z","link_external":false,"link_url":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","apiSetting":null,"auth":"required","params":[],"url":""},"isReference":false,"order":20,"body":"[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/83fd4bf-Dec-16-2016_20-29-53.gif\",\n        \"Dec-16-2016 20-29-53.gif\",\n        480,\n        846,\n        \"#dedde3\"\n      ]\n    }\n  ]\n}\n[/block]\nAs of iOS 10, Apple gave developers the ability to produce Rich Notifications. These are capable of displaying attachments (thumbnails), or displaying media such as images, animated GIFs and videos. You can also add adding a custom UI to your rich notifications. You can send and activate Rich Notifications using Carnival.\n\nBecause your app can receive Rich Notifications alongside regular push notifications, you need to complete a few steps to tell your app how to determine when to display attachments:\n\n1. **Add a Notification Service Extension** to enable Rich Notifications\n2. **Implement the Extension code** and write the logic to download and display the attachment\n3. **Send Messages with Pushes attached** with Carnival dashboard\n\n### Add a Notification Service Extension\niOS 10 brings two new app extensions for push notifications: a Notification Service Extension and a Notification Content Extensions. \n\nTo display basic attachments such as images or animated GIFs, you will only need a Notification Services Extension. This extension is activated as the notification arrives but before it is presented to the user. You have about 30 seconds to modify the push notification content such as text or attachments and then present it to the user.\n\nTo start, add a Notification Service Extension Target to your application by choosing File, New, Target:\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/1bf6274-Screen_Shot_2016-11-04_at_1.38.03_PM.png\",\n        \"Screen Shot 2016-11-04 at 1.38.03 PM.png\",\n        732,\n        520,\n        \"#eaebeb\"\n      ],\n      \"caption\": \"Choosing Notification Service Extension to add as a new Target.\"\n    }\n  ]\n}\n[/block]\n### Enable Push Notifications\nIf you have not done so already, you need to enable Push Notifications as a capability and set up provisioning, which is similar to [setting up basic iOS push](doc:push-notifications-for-ios).\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/9f8bdeb-Screen_Shot_2016-11-01_at_9.39.22_AM.png\",\n        \"Screen Shot 2016-11-01 at 9.39.22 AM.png\",\n        1166,\n        226,\n        \"#dbdbd8\"\n      ],\n      \"caption\": \"Turning on Push in the Capabilities screen of your target.\"\n    }\n  ]\n}\n[/block]\n### Implement the Extension Code\n\nYou need to write code so that your Service Extension can download and handle the attachment you want to display.\n\nIn this example, we want to attach a GIF to our Rich Notification. To do so, we will send the URL to the image using a payload attribute we call `image_url`. Our Service Extension is already configured to accept videos too. If you wish, you can pass a valid video stream to the `video_url` payload attribute. If you specify both, our Service Extension will display the video.\n\nNotice how we're not writing code to size and position our image, as iOS takes care of that automatically. Your code will only need to take care of downloading the resource and saving it to a temporary location.\n\nInside the Service Extension, modify your code to look like the below code block. This will download a resource (image, video, GIF) that you include in the push payload. It will then write it to the internal storage and attach it to the push notification.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"#import \\\"NotificationService.h\\\"\\n\\n:::at:::interface NotificationService ()\\n\\n@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);\\n@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;\\n@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;\\n\\n@end\\n\\n@implementation NotificationService\\n\\n- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {\\n    self.contentHandler = contentHandler;\\n    self.bestAttemptContent = [request.content mutableCopy];\\n    \\n    // Modify the notification content here...\\n    [self carnivalRichNotificationAttachments:self.bestAttemptContent withResponse:^(UNMutableNotificationContent * _Nullable content) {\\n        self.bestAttemptContent = content;\\n        self.contentHandler(self.bestAttemptContent);\\n    }];\\n}\\n\\n- (void)serviceExtensionTimeWillExpire {\\n    // Called just before the extension will be terminated by the system.\\n    // Use this as an opportunity to deliver your \\\"best attempt\\\" at modified content, otherwise the original push payload will be used.\\n    [self.downloadTask cancel];\\n    \\n    self.contentHandler(self.bestAttemptContent);\\n}\\n\\n- (void)carnivalRichNotificationAttachments:(UNMutableNotificationContent *)originalContent withResponse:(nullable void(^)(UNMutableNotificationContent *__nullable modifiedContent))block  {\\n    // For Image or Video in-app messages, we will send the media URL in the\\n    // _st payload\\n    NSString *imageURL = originalContent.userInfo[@\\\"_st\\\"][@\\\"image_url\\\"];\\n    NSString *videoURL = originalContent.userInfo[@\\\"_st\\\"][@\\\"video_url\\\"];\\n    \\n    NSURL *attachmentURL = nil;\\n    if (videoURL && ![videoURL isKindOfClass:[NSNull class]]) { //Prioritize videos over image\\n        attachmentURL = [NSURL URLWithString:videoURL];\\n    }\\n    else if (imageURL && ![imageURL isKindOfClass:[NSNull class]]) {\\n        attachmentURL = [NSURL URLWithString:imageURL];\\n    }\\n    else {\\n        block(originalContent); //Nothing to add to the push, return early.\\n        return;\\n    }\\n    \\n    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];\\n    self.downloadTask = [session downloadTaskWithURL:attachmentURL completionHandler:^(NSURL *fileLocation, NSURLResponse *response, NSError *error) {\\n        if (error != nil) {\\n            block(originalContent); //Nothing to add to the push, return early.\\n            return;\\n        }\\n        else {\\n            NSFileManager *fileManager = [NSFileManager defaultManager];\\n            NSString *fileSuffix = attachmentURL.lastPathComponent;\\n            \\n            NSURL *typedAttachmentURL = [NSURL fileURLWithPath:[(NSString *_Nonnull)fileLocation.path stringByAppendingString:fileSuffix]];\\n            [fileManager moveItemAtURL:fileLocation toURL:typedAttachmentURL error:&error];\\n            \\n            NSError *attachError = nil;\\n            UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@\\\"\\\" URL:typedAttachmentURL options:nil error:&attachError];\\n            \\n            if (attachment == nil) {\\n                block(originalContent); //Nothing to add to the push, return early.\\n                return;\\n            }\\n            \\n            UNMutableNotificationContent *modifiedContent = originalContent.mutableCopy;\\n            [modifiedContent setAttachments:[NSArray arrayWithObject:attachment]];\\n            block(modifiedContent);\\n        }\\n    }];\\n    [self.downloadTask resume];\\n}\\n\\n@end\\n\",\n      \"language\": \"objectivec\",\n      \"name\": \"iOS (Objective-C)\"\n    },\n    {\n      \"code\": \"import UserNotifications\\n\\nclass NotificationService: UNNotificationServiceExtension {\\n    \\n    var contentHandler: ((UNNotificationContent) -> Void)?\\n    var bestAttemptContent: UNMutableNotificationContent?\\n    var downloadTask: URLSessionDownloadTask?\\n    \\n    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {\\n        self.contentHandler = contentHandler\\n        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)\\n        \\n        if let bestAttemptContent = bestAttemptContent {\\n            if let title = bestAttemptContent.userInfo[\\\"title\\\"] {\\n                bestAttemptContent.title = title as! String\\n            }\\n            \\n            var urlString:String?\\n            \\n            // Prioritize video over image\\n            if let videoURL = bestAttemptContent.userInfo[\\\"_st\\\"]?[\\\"video_url\\\"] {\\n                urlString = videoURL as? String\\n            } else if let imageURL = bestAttemptContent.userInfo[\\\"_st\\\"]?[\\\"image_url\\\"] {\\n                urlString = imageURL as? String\\n            } else {\\n                // Nothing to add to the push, return early.\\n                contentHandler(bestAttemptContent)\\n                return\\n            }\\n            \\n            carnivalHandleAttachmentDownload(content: bestAttemptContent.userInfo, urlString: urlString!)\\n    \\n        }\\n    }\\n    \\n    func carnivalHandleAttachmentDownload(content: [AnyHashable : Any], urlString: String) {\\n        \\n        guard let url = URL(string: urlString) else {\\n            // Cannot create a valid URL, return early.\\n            self.contentHandler!(self.bestAttemptContent!)\\n            return\\n        }\\n        \\n        self.downloadTask = URLSession.shared.downloadTask(with: url) { (location, response, error) in\\n            if let location = location {\\n                let tmpDirectory = NSTemporaryDirectory()\\n                let tmpFile = \\\"file://\\\".appending(tmpDirectory).appending(url.lastPathComponent)\\n                \\n                let tmpUrl = URL(string: tmpFile)!\\n                try! FileManager.default.moveItem(at: location, to: tmpUrl)\\n                \\n                if let attachment = try? UNNotificationAttachment(identifier: \\\"\\\", url: tmpUrl) {\\n                    self.bestAttemptContent?.attachments = [attachment]\\n                }\\n            }\\n            \\n            self.contentHandler!(self.bestAttemptContent!)\\n        }\\n        \\n        self.downloadTask?.resume()\\n    }\\n    \\n    override func serviceExtensionTimeWillExpire() {\\n        // Called just before the extension will be terminated by the system.\\n        // Use this as an opportunity to deliver your \\\"best attempt\\\" at modified content, otherwise the original push payload will be used.\\n        self.downloadTask?.cancel()\\n        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {\\n            contentHandler(bestAttemptContent)\\n        }\\n    }\\n}\",\n      \"language\": \"swift\",\n      \"name\": \"iOS (Swift 3)\"\n    }\n  ]\n}\n[/block]\n### Send Rich Notifications\n\nWhen creating an in-app message with push notification via the Carnival Web Interface, the image added to the in-app message will automatically be delivered to the iOS device inside the `UNMutableNotificationContent` `_st` payload. If using the extension code above, this will then be displayed as a Rich Notification on the iOS device.\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/cb9cffe-131d415-Screen_Shot_2018-05-08_at_1.45.22_PM.png\",\n        \"131d415-Screen_Shot_2018-05-08_at_1.45.22_PM.png\",\n        1105,\n        523,\n        \"#e3e8ee\"\n      ],\n      \"caption\": \"Creating a message with an image inside the Carnival platform.\"\n    }\n  ]\n}\n[/block]\nAlternatively, [rich notifications can be sent using the API as per the Rich Push example](https://docs.carnival.io/docs/notifications).\n\n\n### Setting Category Identifiers\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"\",\n  \"body\": \"Categories are optional and do not need to be included. If you do, however, the push notification service extension **will require** that category to activate.\"\n}\n[/block]\nYou can set a Category Identifier for your Notification to respond to. This identifier contains a string value you determine, and it will be used by the Service Extension to determine how it should respond to this notification category (or if it should respond at all). You will use this identifier to register for push notifications and include it in your payload when sending.\n\nYou can add one or many categories in the plist of the extension. While normally you will only require one value, multiple identifiers are useful with Content Extensions, when you may want to perform different tasks depending on the UI your app will present to the user.\n\nTo add a Category Identifier, navigate to your Service Extension's Info.plist and add `UNNotificationExtensionCategory` (String) under `NSExtension` -> `NSExtensionAttributes`:\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/cd249b9-Screen_Shot_2016-11-04_at_1.57.26_PM.png\",\n        \"Screen Shot 2016-11-04 at 1.57.26 PM.png\",\n        708,\n        94,\n        \"#33446c\"\n      ],\n      \"caption\": \"A single category identifier registered.\"\n    }\n  ]\n}\n[/block]\nIf you need to specify multiple values, change to `UNNotificationExtensionCategory` to be an Array:\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/cd2e161-Screen_Shot_2016-11-04_at_2.09.13_PM.png\",\n        \"Screen Shot 2016-11-04 at 2.09.13 PM.png\",\n        596,\n        146,\n        \"#2f355a\"\n      ],\n      \"caption\": \"Multiple category identifiers registered. Change the UNNotificationExtensionCategory type to Array from String to add multiple values\"\n    }\n  ]\n}\n[/block]","excerpt":"","slug":"ios-rich-push","type":"basic","title":"iOS: Rich Notifications"}

iOS: Rich Notifications


[block:image] { "images": [ { "image": [ "https://files.readme.io/83fd4bf-Dec-16-2016_20-29-53.gif", "Dec-16-2016 20-29-53.gif", 480, 846, "#dedde3" ] } ] } [/block] As of iOS 10, Apple gave developers the ability to produce Rich Notifications. These are capable of displaying attachments (thumbnails), or displaying media such as images, animated GIFs and videos. You can also add adding a custom UI to your rich notifications. You can send and activate Rich Notifications using Carnival. Because your app can receive Rich Notifications alongside regular push notifications, you need to complete a few steps to tell your app how to determine when to display attachments: 1. **Add a Notification Service Extension** to enable Rich Notifications 2. **Implement the Extension code** and write the logic to download and display the attachment 3. **Send Messages with Pushes attached** with Carnival dashboard ### Add a Notification Service Extension iOS 10 brings two new app extensions for push notifications: a Notification Service Extension and a Notification Content Extensions. To display basic attachments such as images or animated GIFs, you will only need a Notification Services Extension. This extension is activated as the notification arrives but before it is presented to the user. You have about 30 seconds to modify the push notification content such as text or attachments and then present it to the user. To start, add a Notification Service Extension Target to your application by choosing File, New, Target: [block:image] { "images": [ { "image": [ "https://files.readme.io/1bf6274-Screen_Shot_2016-11-04_at_1.38.03_PM.png", "Screen Shot 2016-11-04 at 1.38.03 PM.png", 732, 520, "#eaebeb" ], "caption": "Choosing Notification Service Extension to add as a new Target." } ] } [/block] ### Enable Push Notifications If you have not done so already, you need to enable Push Notifications as a capability and set up provisioning, which is similar to [setting up basic iOS push](doc:push-notifications-for-ios). [block:image] { "images": [ { "image": [ "https://files.readme.io/9f8bdeb-Screen_Shot_2016-11-01_at_9.39.22_AM.png", "Screen Shot 2016-11-01 at 9.39.22 AM.png", 1166, 226, "#dbdbd8" ], "caption": "Turning on Push in the Capabilities screen of your target." } ] } [/block] ### Implement the Extension Code You need to write code so that your Service Extension can download and handle the attachment you want to display. In this example, we want to attach a GIF to our Rich Notification. To do so, we will send the URL to the image using a payload attribute we call `image_url`. Our Service Extension is already configured to accept videos too. If you wish, you can pass a valid video stream to the `video_url` payload attribute. If you specify both, our Service Extension will display the video. Notice how we're not writing code to size and position our image, as iOS takes care of that automatically. Your code will only need to take care of downloading the resource and saving it to a temporary location. Inside the Service Extension, modify your code to look like the below code block. This will download a resource (image, video, GIF) that you include in the push payload. It will then write it to the internal storage and attach it to the push notification. [block:code] { "codes": [ { "code": "#import \"NotificationService.h\"\n\n@interface NotificationService ()\n\n@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);\n@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;\n@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;\n\n@end\n\n@implementation NotificationService\n\n- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {\n self.contentHandler = contentHandler;\n self.bestAttemptContent = [request.content mutableCopy];\n \n // Modify the notification content here...\n [self carnivalRichNotificationAttachments:self.bestAttemptContent withResponse:^(UNMutableNotificationContent * _Nullable content) {\n self.bestAttemptContent = content;\n self.contentHandler(self.bestAttemptContent);\n }];\n}\n\n- (void)serviceExtensionTimeWillExpire {\n // Called just before the extension will be terminated by the system.\n // Use this as an opportunity to deliver your \"best attempt\" at modified content, otherwise the original push payload will be used.\n [self.downloadTask cancel];\n \n self.contentHandler(self.bestAttemptContent);\n}\n\n- (void)carnivalRichNotificationAttachments:(UNMutableNotificationContent *)originalContent withResponse:(nullable void(^)(UNMutableNotificationContent *__nullable modifiedContent))block {\n // For Image or Video in-app messages, we will send the media URL in the\n // _st payload\n NSString *imageURL = originalContent.userInfo[@\"_st\"][@\"image_url\"];\n NSString *videoURL = originalContent.userInfo[@\"_st\"][@\"video_url\"];\n \n NSURL *attachmentURL = nil;\n if (videoURL && ![videoURL isKindOfClass:[NSNull class]]) { //Prioritize videos over image\n attachmentURL = [NSURL URLWithString:videoURL];\n }\n else if (imageURL && ![imageURL isKindOfClass:[NSNull class]]) {\n attachmentURL = [NSURL URLWithString:imageURL];\n }\n else {\n block(originalContent); //Nothing to add to the push, return early.\n return;\n }\n \n NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];\n self.downloadTask = [session downloadTaskWithURL:attachmentURL completionHandler:^(NSURL *fileLocation, NSURLResponse *response, NSError *error) {\n if (error != nil) {\n block(originalContent); //Nothing to add to the push, return early.\n return;\n }\n else {\n NSFileManager *fileManager = [NSFileManager defaultManager];\n NSString *fileSuffix = attachmentURL.lastPathComponent;\n \n NSURL *typedAttachmentURL = [NSURL fileURLWithPath:[(NSString *_Nonnull)fileLocation.path stringByAppendingString:fileSuffix]];\n [fileManager moveItemAtURL:fileLocation toURL:typedAttachmentURL error:&error];\n \n NSError *attachError = nil;\n UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@\"\" URL:typedAttachmentURL options:nil error:&attachError];\n \n if (attachment == nil) {\n block(originalContent); //Nothing to add to the push, return early.\n return;\n }\n \n UNMutableNotificationContent *modifiedContent = originalContent.mutableCopy;\n [modifiedContent setAttachments:[NSArray arrayWithObject:attachment]];\n block(modifiedContent);\n }\n }];\n [self.downloadTask resume];\n}\n\n@end\n", "language": "objectivec", "name": "iOS (Objective-C)" }, { "code": "import UserNotifications\n\nclass NotificationService: UNNotificationServiceExtension {\n \n var contentHandler: ((UNNotificationContent) -> Void)?\n var bestAttemptContent: UNMutableNotificationContent?\n var downloadTask: URLSessionDownloadTask?\n \n override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {\n self.contentHandler = contentHandler\n bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)\n \n if let bestAttemptContent = bestAttemptContent {\n if let title = bestAttemptContent.userInfo[\"title\"] {\n bestAttemptContent.title = title as! String\n }\n \n var urlString:String?\n \n // Prioritize video over image\n if let videoURL = bestAttemptContent.userInfo[\"_st\"]?[\"video_url\"] {\n urlString = videoURL as? String\n } else if let imageURL = bestAttemptContent.userInfo[\"_st\"]?[\"image_url\"] {\n urlString = imageURL as? String\n } else {\n // Nothing to add to the push, return early.\n contentHandler(bestAttemptContent)\n return\n }\n \n carnivalHandleAttachmentDownload(content: bestAttemptContent.userInfo, urlString: urlString!)\n \n }\n }\n \n func carnivalHandleAttachmentDownload(content: [AnyHashable : Any], urlString: String) {\n \n guard let url = URL(string: urlString) else {\n // Cannot create a valid URL, return early.\n self.contentHandler!(self.bestAttemptContent!)\n return\n }\n \n self.downloadTask = URLSession.shared.downloadTask(with: url) { (location, response, error) in\n if let location = location {\n let tmpDirectory = NSTemporaryDirectory()\n let tmpFile = \"file://\".appending(tmpDirectory).appending(url.lastPathComponent)\n \n let tmpUrl = URL(string: tmpFile)!\n try! FileManager.default.moveItem(at: location, to: tmpUrl)\n \n if let attachment = try? UNNotificationAttachment(identifier: \"\", url: tmpUrl) {\n self.bestAttemptContent?.attachments = [attachment]\n }\n }\n \n self.contentHandler!(self.bestAttemptContent!)\n }\n \n self.downloadTask?.resume()\n }\n \n override func serviceExtensionTimeWillExpire() {\n // Called just before the extension will be terminated by the system.\n // Use this as an opportunity to deliver your \"best attempt\" at modified content, otherwise the original push payload will be used.\n self.downloadTask?.cancel()\n if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {\n contentHandler(bestAttemptContent)\n }\n }\n}", "language": "swift", "name": "iOS (Swift 3)" } ] } [/block] ### Send Rich Notifications When creating an in-app message with push notification via the Carnival Web Interface, the image added to the in-app message will automatically be delivered to the iOS device inside the `UNMutableNotificationContent` `_st` payload. If using the extension code above, this will then be displayed as a Rich Notification on the iOS device. [block:image] { "images": [ { "image": [ "https://files.readme.io/cb9cffe-131d415-Screen_Shot_2018-05-08_at_1.45.22_PM.png", "131d415-Screen_Shot_2018-05-08_at_1.45.22_PM.png", 1105, 523, "#e3e8ee" ], "caption": "Creating a message with an image inside the Carnival platform." } ] } [/block] Alternatively, [rich notifications can be sent using the API as per the Rich Push example](https://docs.carnival.io/docs/notifications). ### Setting Category Identifiers [block:callout] { "type": "info", "title": "", "body": "Categories are optional and do not need to be included. If you do, however, the push notification service extension **will require** that category to activate." } [/block] You can set a Category Identifier for your Notification to respond to. This identifier contains a string value you determine, and it will be used by the Service Extension to determine how it should respond to this notification category (or if it should respond at all). You will use this identifier to register for push notifications and include it in your payload when sending. You can add one or many categories in the plist of the extension. While normally you will only require one value, multiple identifiers are useful with Content Extensions, when you may want to perform different tasks depending on the UI your app will present to the user. To add a Category Identifier, navigate to your Service Extension's Info.plist and add `UNNotificationExtensionCategory` (String) under `NSExtension` -> `NSExtensionAttributes`: [block:image] { "images": [ { "image": [ "https://files.readme.io/cd249b9-Screen_Shot_2016-11-04_at_1.57.26_PM.png", "Screen Shot 2016-11-04 at 1.57.26 PM.png", 708, 94, "#33446c" ], "caption": "A single category identifier registered." } ] } [/block] If you need to specify multiple values, change to `UNNotificationExtensionCategory` to be an Array: [block:image] { "images": [ { "image": [ "https://files.readme.io/cd2e161-Screen_Shot_2016-11-04_at_2.09.13_PM.png", "Screen Shot 2016-11-04 at 2.09.13 PM.png", 596, 146, "#2f355a" ], "caption": "Multiple category identifiers registered. Change the UNNotificationExtensionCategory type to Array from String to add multiple values" } ] } [/block]