Include images, videos, titles, and subtitles in your push notifications to create more engaging messages. Rich notifications use mutable content that can include personalization in the title, subtitle, or body of your message.
To use rich notifications, your app requires iOS 10 or later, a Notification Service Extension that handles mutable content, and registration for push notifications via the Salesforce Engagement SDK. The service extension downloads media from a URL, creates attachments, and handles fallback text if media fails to load.
In your project target’s General settings, confirm that the new extension is listed in the Frameworks, Libraries, and Embedded Content section. If it isn’t, add it.
Sign the Notification Target with the same Xcode Managed Profile as the main project.
Important
This service extension checks for a _mediaUrl element in request.content.userInfo. If the extension finds this element, it tries to download the media from the URL, creates a thumbnail version, and then adds the attachment. The service extension also checks for a _mediaAlt element in request.content.userInfo. If found, the service extension uses the element for the body text if there are any problems downloading or creating the media attachment.
A service extension can timeout if it’s unable to download. In this code sample, the service extension delivers the original content with the body text changed to the value in _mediaAlt.
1import UserNotifications2import CoreGraphics34class MyNotificationService: UNNotificationServiceExtension {56 var contentHandler: ((UNNotificationContent) ->Void)?7 var bestAttemptContent: UNMutableNotificationContent?89 override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping(UNNotificationContent) ->Void){10 self.contentHandler = contentHandler11 self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)1213 self.addMedia(request){14 self.contentHandler!(self.bestAttemptContent!)15}16}1718 override func serviceExtensionTimeWillExpire(){19 // Called just before the extension is terminated by the system.20 // Use this as an opportunity to deliver your "best attempt" at modified content. Otherwise, the original push payload is used.2122 // We took too long to download the media URL. Use the alternate text if provided.23 self.useAltText()2425 // Tell the OS that the process is complete and the content is ready to be presented.26 self.contentHandler!(self.bestAttemptContent!)27}2829 private func useAltText(){30 if let mediaAltText = self.bestAttemptContent?.userInfo["_mediaAlt"] as? String{31 // alternative text to display if there are any issues loading the media URL32 if mediaAltText.isEmpty == false{33 self.bestAttemptContent?.body = mediaAltText34}35}36}3738 private func addMedia(_ request: UNNotificationRequest, completion: @escaping() ->Void){39 guard let mediaUrlString = request.content.userInfo["_mediaUrl"] as? String,40 !mediaUrlString.isEmpty else{41 useAltText()42 completion()43 return44}4546 guard let mediaUrl = URL(string: mediaUrlString)else{47 useAltText()48 completion()49 return50}5152 let session = URLSession(configuration: URLSessionConfiguration.default)53 let downloadTask = session.downloadTask(with: mediaUrl){[weak self]54 location, response, error in55 if let _ = error {56 self?.useAltText()57 completion()58 return59}6061 guard let theLocation = location else{62 self?.useAltText()63 completion()64 return65}6667 guard let theResponse = response as? HTTPURLResponse else{68 self?.useAltText()69 completion()70 return71}7273 let statusCode = theResponse.statusCode74 guard(statusCode >= 200 && statusCode <= 299)else{75 self?.useAltText()76 completion()77 return78}7980 let localMediaUrl = URL.init(fileURLWithPath: theLocation.path + mediaUrl.lastPathComponent)8182 // Remove any existing file with the same name83 try? FileManager.default.removeItem(at: localMediaUrl)8485 do{86 try FileManager.default.moveItem(at: theLocation, to: localMediaUrl)87}catch{88 self?.useAltText()89 completion()90 return91}9293 guard let mediaAttachment = try? UNNotificationAttachment(identifier: "SomeAttachmentId",94 url: localMediaUrl)else{95 self?.useAltText()96 completion()97 return;98}99100 guard let theContent = self?.bestAttemptContent else{101 self?.useAltText()102 completion()103 return104}105106 theContent.attachments = [mediaAttachment]107 completion()108}109110 downloadTask.resume()111}112}
1//2// NotificationService.swift3// MyServiceExtension4//5//67import UserNotifications8import MCExtensionSDK910class NotificationService: SFMCNotificationService {1112 // Use this method to enable logging, change logging levels, etc. , if required.13 override func sfmcProvideConfig() -> SFNotificationServiceConfig {14 var logLevel: LogLevel = .none15#if DEBUG16 logLevel = .debug17#endif18 return SFNotificationServiceConfig(logLevel: logLevel)19}2021 // To perform custom processing for images, video downloads, or inserting custom keys in notification userInfo, use this method.22 // Don’t modify `mutableContent.request.content.userInfo` directly. Doing so can trigger an exception.23 // To add a custom key in notification userInfo, return a dictionary in the completion handler.242526 // Like the `UNNotificationServiceExtension` method - func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) → Void), the system allows only a limited time for processing.27 override func sfmcDidReceive(_ request: UNNotificationRequest, mutableContent: UNMutableNotificationContent, withContentHandler contentHandler: @escaping([AnyHashable : Any]?) ->Void){28 // Your custom code here29 //...30 self.addMedia(mutableContent){31 // Add custom key:value pairs in the userInfo object32 var customUserInfo: [AnyHashable : Any] = [:]33 customUserInfo["MyCustomKey"] = "MyCustomValue"3435 // Finally, call the content handler to signal the end of your processing operation.36 contentHandler(customUserInfo)37}38}3940 private func addMedia(_ mutableContent: UNMutableNotificationContent, completion: @escaping() ->Void){41 guard let mediaUrlString = mutableContent.userInfo["_mediaUrl"] as? String,42 !mediaUrlString.isEmpty else{43 completion()44 return45}4647 guard let mediaUrl = URL(string: mediaUrlString)else{48 completion()49 return50}5152 let session = URLSession(configuration: URLSessionConfiguration.default)53 let downloadTask = session.downloadTask(with: mediaUrl){[weak self]54 location, response, error in55 if let _ = error {56 completion()57 return58}5960 guard let theLocation = location else{61 completion()62 return63}6465 guard let theResponse = response as? HTTPURLResponse else{66 completion()67 return68}6970 let statusCode = theResponse.statusCode71 guard(statusCode >= 200 && statusCode <= 299)else{72 completion()73 return74}7576 let localMediaUrl = URL.init(fileURLWithPath: theLocation.path + mediaUrl.lastPathComponent)7778 // Remove any existing file with the same name79 try? FileManager.default.removeItem(at: localMediaUrl)8081 do{82 try FileManager.default.moveItem(at: theLocation, to: localMediaUrl)83}catch{84 completion()85 return86}8788 guard let mediaAttachment = try? UNNotificationAttachment(identifier: "SomeAttachmentId",89 url: localMediaUrl)else{90 completion()91 return;92}9394 mutableContent.attachments = [mediaAttachment]95 completion()96}9798 downloadTask.resume()99}100}