Integrate the Android SDK
Runtime Toggles
Enable iOS Push Notifications
Customize Sounds
Display Interactive Notifications
Send Rich Notifications
Handle URLs
Integrate with Other Platforms
Handle URLs
Changelog
Rich notifications include images, videos, titles, and subtitles from Marketing Cloud Next and mutable content. Mutable content can include personalization in the title, subtitle, or body of your message.
Skip these steps if you’ve already integrated Notification Service Extension when you integrated the Mobile App Messaging SDK with your app.
The Notification Target must be signed with the same Xcode Managed Profile as the main project.
Important
This service extension checks for a _mediaUrl element in request.content.userInfo. If found, the extension attempts to download the media from the URL, creates a thumbnail-size 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. This code example assumes your app is using the Mobile App Messaging SDK without the Extension SDK integration.
1import UserNotifications
2import CoreGraphics
3
4class MyNotificationService: UNNotificationServiceExtension {
5
6 var contentHandler: ((UNNotificationContent) -> Void)?
7 var bestAttemptContent: UNMutableNotificationContent?
8
9 override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
10 self.contentHandler = contentHandler
11 self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
12
13 self.addMedia(request) {
14 self.contentHandler!(self.bestAttemptContent!)
15 }
16 }
17
18 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 will be used.
21 // We took too long to download the media URL. Use the alternate text if provided.
22 self.useAltText()
23
24 // Tell the OS that the process is complete and the content is ready to be presented.
25 self.contentHandler!(self.bestAttemptContent!)
26 }
27
28 private func useAltText() {
29 if let mediaAltText = self.bestAttemptContent?.userInfo["_mediaAlt"] as? String {
30 // alternative text to display if there are any issues loading the media URL
31 if mediaAltText.isEmpty == false {
32 self.bestAttemptContent?.body = mediaAltText
33 }
34 }
35 }
36
37 private func addMedia(_ request: UNNotificationRequest, completion: @escaping () -> Void) {
38 guard let mediaUrlString = request.content.userInfo["_mediaUrl"] as? String,
39 !mediaUrlString.isEmpty else {
40 useAltText()
41 completion()
42 return
43 }
44
45 guard let mediaUrl = URL(string: mediaUrlString) else {
46 useAltText()
47 completion()
48 return
49 }
50
51 let session = URLSession(configuration: URLSessionConfiguration.default)
52 let downloadTask = session.downloadTask(with: mediaUrl) { [weak self]
53 location, response, error in
54 if let _ = error {
55 self?.useAltText()
56 completion()
57 return
58 }
59
60 guard let theLocation = location else {
61 self?.useAltText()
62 completion()
63 return
64 }
65
66 guard let theResponse = response as? HTTPURLResponse else {
67 self?.useAltText()
68 completion()
69 return
70 }
71
72 let statusCode = theResponse.statusCode
73 guard (statusCode >= 200 && statusCode <= 299) else {
74 self?.useAltText()
75 completion()
76 return
77 }
78
79 let localMediaUrl = URL.init(fileURLWithPath: theLocation.path + mediaUrl.lastPathComponent)
80
81 // Remove any existing file with the same name
82 try? FileManager.default.removeItem(at: localMediaUrl)
83
84 do {
85 try FileManager.default.moveItem(at: theLocation, to: localMediaUrl)
86 } catch {
87 self?.useAltText()
88 completion()
89 return
90 }
91
92 guard let mediaAttachment = try? UNNotificationAttachment(identifier: "SomeAttachmentId",
93 url: localMediaUrl) else {
94 self?.useAltText()
95 completion()
96 return;
97 }
98
99 guard let theContent = self?.bestAttemptContent else {
100 self?.useAltText()
101 completion()
102 return
103 }
104
105 theContent.attachments = [mediaAttachment]
106 completion()
107 }
108
109 downloadTask.resume()
110 }
111}If your app uses the Mobile App Messaging SDK with the Extension SDK integration, use this code example instead.
1// NotificationService.swift
2// MyServiceExtension
3
4import UserNotifications
5import MCExtensionSDK
6
7class NotificationService: SFMCNotificationService {
8
9 // Use this method to enable logging, change logging levels, etc. , if required.
10 override func sfmcProvideConfig() -> SFNotificationServiceConfig {
11 var logLevel: LogLevel = .none
12#if DEBUG
13 logLevel = .debug
14#endif
15 return SFNotificationServiceConfig(logLevel: logLevel)
16 }
17
18 // Use this method only if you need to perform any custom processing for
19 // images, video downloads, inserting custom keys in notification userInfo, etc.
20 // Don’t modify mutableContent.request.content.userInfo directly. To add any
21 // custom key in notification userInfo, return a dictionary in the
22 // completion handler.
23
24
25 // Like the `UNNotificationServiceExtension` method - func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) → Void), you'll only have limited time from the system for processing.
26 override func sfmcDidReceive(_ request: UNNotificationRequest, mutableContent: UNMutableNotificationContent, withContentHandler contentHandler: @escaping ([AnyHashable : Any]?) -> Void) {
27 // Your custom code here
28 //...
29 self.addMedia(mutableContent) {
30 // To add any custom key:value pair(s) in notifications userInfo object, then
31 var customUserInfo: [AnyHashable : Any] = [:]
32 customUserInfo["MyCustomKey"] = "MyCustomValue"
33
34 // Finally, call the content handler to signal the end of your processing operation.
35 //
36 contentHandler(customUserInfo)
37 }
38 }
39
40 private func addMedia(_ mutableContent: UNMutableNotificationContent, completion: @escaping () -> Void) {
41 guard let mediaUrlString = mutableContent.userInfo["_mediaUrl"] as? String,
42 !mediaUrlString.isEmpty else {
43 completion()
44 return
45 }
46
47 guard let mediaUrl = URL(string: mediaUrlString) else {
48 completion()
49 return
50 }
51
52 let session = URLSession(configuration: URLSessionConfiguration.default)
53 let downloadTask = session.downloadTask(with: mediaUrl) { [weak self]
54 location, response, error in
55 if let _ = error {
56 completion()
57 return
58 }
59
60 guard let theLocation = location else {
61 completion()
62 return
63 }
64
65 guard let theResponse = response as? HTTPURLResponse else {
66 completion()
67 return
68 }
69
70 let statusCode = theResponse.statusCode
71 guard (statusCode >= 200 && statusCode <= 299) else {
72 completion()
73 return
74 }
75
76 let localMediaUrl = URL.init(fileURLWithPath: theLocation.path + mediaUrl.lastPathComponent)
77
78 // Remove any existing file with the same name
79 try? FileManager.default.removeItem(at: localMediaUrl)
80
81 do {
82 try FileManager.default.moveItem(at: theLocation, to: localMediaUrl)
83 } catch {
84 completion()
85 return
86 }
87
88 guard let mediaAttachment = try? UNNotificationAttachment(identifier: "SomeAttachmentId",
89 url: localMediaUrl) else {
90 completion()
91 return;
92 }
93
94 mutableContent.attachments = [mediaAttachment]
95 completion()
96 }
97
98 downloadTask.resume()
99 }
100}