{"_id":"581ab50a80c5890f00d72d9b","user":"55d29988486de50d00327118","__v":0,"parentDoc":null,"project":"55e67aaa9cc7c62b00c4a1ea","category":{"_id":"589b8e1fdbb7cd190026732c","project":"55e67aaa9cc7c62b00c4a1ea","__v":0,"version":"55e67aab9cc7c62b00c4a1ed","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":"55e67aab9cc7c62b00c4a1ed","project":"55e67aaa9cc7c62b00c4a1ea","__v":10,"createdAt":"2015-09-02T04:27:23.612Z","releaseDate":"2015-09-02T04:27:23.612Z","categories":["55e67aac9cc7c62b00c4a1ee","55e67b5556007d23005fee7d","55e67b5dde6fef23009480ca","55e680efde6fef23009480db","55e6829485a9741900314e99","561c61b4ad272c0d00a892df","586c014c0abf1d0f000d04d4","58991d2ad207df0f0002186b","589b8e1fdbb7cd190026732c","58b8ca5e3265d70f001788d4"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"","version_clean":"1.0.0","version":"1.0"},"updates":[],"next":{"pages":[],"description":""},"createdAt":"2016-11-03T03:54:50.295Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":false,"order":4,"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 carnivalRichNotificationAttachements: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)carnivalRichNotificationAttachements:(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    // You can also specify a media URL by defining your own payload keys\\n    NSString *imageURL = originalContent.userInfo[@\\\"my_image_url\\\"]; \\n    NSString *videoURL = originalContent.userInfo[@\\\"my_video_url\\\"];\\n\\n    NSURL *attachmentURL = nil;\\n    if (![videoURL isKindOfClass:[NSNull class]]) { //Prioritize videos over image\\n        attachmentURL = [NSURL URLWithString:videoURL];\\n    }\\n    else if (![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[\\\"video_url\\\"] {\\n                urlString = videoURL as? String\\n            } else if let imageURL = bestAttemptContent.userInfo[\\\"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\nThe most common way you'll want to send a Rich Notification is via our API. In the notification `payload`, you define a Rich Notification by setting the `mutable_content` flag to `true` and by specifying one of the category identifiers you defined earlier (it must match one of the values you defined in your Service Extension's Info.plist). The Service Extension will also look for the image URL using the `image_url` or `video_url` keys. Make sure you specify these values.\n\nHere is an example:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"curl -X POST -u :API_KEY -H \\\"Content-type: application/json\\\" -H 'Accept: application/json' https://api.carnivalmobile.com/v5/notifications -d '{\\n  \\\"notification\\\": {\\n    \\\"to\\\": [{ \\\"name\\\": \\\"custom.boolean.forecast_user\\\", \\\"criteria\\\": [true]}],\\n    \\\"payload\\\": {\\n      \\\"alert\\\": \\\"Cloudy today. The high will be 55°. Tap to read the forecast for your area.\\\",\\n      \\\"title\\\": \\\"Your weather forecast\\\",\\n      \\\"badge\\\": 1,\\n      \\\"sound\\\": \\\"Default.caf\\\",\\n      \\\"mutable_content\\\": true,\\n\\t    \\\"image_url\\\": \\\"https://example.com/cloudy.gif\\\"\\n    }\\n  }\\n}'\",\n      \"language\": \"curl\",\n      \"name\": \"Rich Push\"\n    }\n  ]\n}\n[/block]\nYou can also send notifications via Dashboard. In the Category field, make sure you use one category identifiers you defined in your Service Extension's Info.plist:\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/f11096b-image_15.png\",\n        \"image_15.png\",\n        589,\n        484,\n        \"#e3ebf2\"\n      ],\n      \"caption\": \"Use Key-Value data to fill in the correct fields and set a category.\"\n    }\n  ]\n}\n[/block]\nIf you create a Video or Image in-app message with a push attached, you will find the URL to the media content inside the `UNMutableNotificationContent`'s `_st` payload.\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]\n\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"curl -X POST -u :API_KEY -H \\\"Content-type: application/json\\\" -H 'Accept: application/json' https://api.carnivalmobile.com/v4/notifications -d '{\\n  \\\"notification\\\": {\\n    \\\"to\\\": [{ \\\"name\\\": \\\"tags\\\", \\\"criteria\\\": [\\\"tag1\\\"]}, { \\\"name\\\": \\\"custom.string.mykey\\\", \\\"criteria\\\": [\\\"tag2\\\"]}],\\n    \\\"payload\\\": {\\n      \\\"alert\\\": \\\"Congratulations Rose, you earned Gold Status. Tap here to discover all the benefits.\\\",\\n      \\\"title\\\": \\\"You just reached Gold Status!\\\",\\n      \\\"badge\\\": 1,\\n      \\\"sound\\\": \\\"Default.caf\\\",\\n      \\\"category\\\": \\\"LOYALTY_MESSAGE\\\",\\n      \\\"mutable_content\\\": true,\\n\\t    \\\"video_url\\\": \\\"https://example.com/gold.mp4\\\"\\n    }\\n  }\\n}'\",\n      \"language\": \"curl\",\n      \"name\": \"iOS Rich Push with Category\"\n    }\n  ]\n}\n[/block]","excerpt":"","slug":"ios-rich-push","type":"basic","title":"iOS 10 Rich Notifications"}

iOS 10 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 carnivalRichNotificationAttachements: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)carnivalRichNotificationAttachements:(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 // You can also specify a media URL by defining your own payload keys\n NSString *imageURL = originalContent.userInfo[@\"my_image_url\"]; \n NSString *videoURL = originalContent.userInfo[@\"my_video_url\"];\n\n NSURL *attachmentURL = nil;\n if (![videoURL isKindOfClass:[NSNull class]]) { //Prioritize videos over image\n attachmentURL = [NSURL URLWithString:videoURL];\n }\n else if (![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[\"video_url\"] {\n urlString = videoURL as? String\n } else if let imageURL = bestAttemptContent.userInfo[\"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 The most common way you'll want to send a Rich Notification is via our API. In the notification `payload`, you define a Rich Notification by setting the `mutable_content` flag to `true` and by specifying one of the category identifiers you defined earlier (it must match one of the values you defined in your Service Extension's Info.plist). The Service Extension will also look for the image URL using the `image_url` or `video_url` keys. Make sure you specify these values. Here is an example: [block:code] { "codes": [ { "code": "curl -X POST -u :API_KEY -H \"Content-type: application/json\" -H 'Accept: application/json' https://api.carnivalmobile.com/v5/notifications -d '{\n \"notification\": {\n \"to\": [{ \"name\": \"custom.boolean.forecast_user\", \"criteria\": [true]}],\n \"payload\": {\n \"alert\": \"Cloudy today. The high will be 55°. Tap to read the forecast for your area.\",\n \"title\": \"Your weather forecast\",\n \"badge\": 1,\n \"sound\": \"Default.caf\",\n \"mutable_content\": true,\n\t \"image_url\": \"https://example.com/cloudy.gif\"\n }\n }\n}'", "language": "curl", "name": "Rich Push" } ] } [/block] You can also send notifications via Dashboard. In the Category field, make sure you use one category identifiers you defined in your Service Extension's Info.plist: [block:image] { "images": [ { "image": [ "https://files.readme.io/f11096b-image_15.png", "image_15.png", 589, 484, "#e3ebf2" ], "caption": "Use Key-Value data to fill in the correct fields and set a category." } ] } [/block] If you create a Video or Image in-app message with a push attached, you will find the URL to the media content inside the `UNMutableNotificationContent`'s `_st` payload. ### 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] [block:code] { "codes": [ { "code": "curl -X POST -u :API_KEY -H \"Content-type: application/json\" -H 'Accept: application/json' https://api.carnivalmobile.com/v4/notifications -d '{\n \"notification\": {\n \"to\": [{ \"name\": \"tags\", \"criteria\": [\"tag1\"]}, { \"name\": \"custom.string.mykey\", \"criteria\": [\"tag2\"]}],\n \"payload\": {\n \"alert\": \"Congratulations Rose, you earned Gold Status. Tap here to discover all the benefits.\",\n \"title\": \"You just reached Gold Status!\",\n \"badge\": 1,\n \"sound\": \"Default.caf\",\n \"category\": \"LOYALTY_MESSAGE\",\n \"mutable_content\": true,\n\t \"video_url\": \"https://example.com/gold.mp4\"\n }\n }\n}'", "language": "curl", "name": "iOS Rich Push with Category" } ] } [/block]