Maropost's Mobile SDK for iOS requires Xcode version 8.0.0 or above.
Mouse over 'Mobile' from the menu bar. Next, select PUSH to view the mobile application index screen.
Upload the Push Notification Client SSL Certificate
Click the Edit icon for the mobile application, then click the iOS tab.
- iOS App Bundle Identifier - This will always be com.development.maropost
- Push Certificate - Create your push notification client SSL certificate. You can then export the certificate as Personal Information Exchange format (.p12 file extension) or as Privacy Enhanced Mail format (.pem file extension). Upload the file where shown in the screen.
- Push Key PEM - Upload the .pem file containing the Push Key
- Certificate Password - Enter the push notification client SSL certificate key here.
Then click the [Save] button.
Install the Maropost Mobile SDK for iOS
- Click on the name of your mobile app in the Push Applications index screen.
- Next, click the Details tab.
- Select which version of the Maropost Mobile SDK for iOS you need. Your options are either Xcode 9 (Swift 4.1.2 and below), or Xcode 10 (Swift 4.2.1).
- Click the [Bitcode Disabled] or the [Bitcode Enabled] button to download the SDK to your desktop.
- If you are downloading the Bitcode-enabled SDK, then rename the MPPush_bitcode.framework file to MPPush.framework.
- Drag MPPush.framework into the top level of your app:
Choose the following options when adding the framework:
Add Maropost's MPPush.framework in the Embedded Frameworks section of the Project target's general settings:
Add these required frameworks to your project:
- CoreLocation.framework
- SystemConfiguration.framework
The framework supports both device and simulator architectures. However, since simulator architectures aren’t supported while publishing the app to the AppStore, you will need to add the following script in the Run Script section of Build Phases in your project settings. This script will ensure that simulator architectures get stripped away at the time of archiving
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}" # This script loops through the frameworks embedded in the application and # removes unused architectures. find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK do FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable) FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME" echo "Executable is $FRAMEWORK_EXECUTABLE_PATH" EXTRACTED_ARCHS=() for ARCH in $ARCHS do echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME" lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH" EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH") done echo "Merging extracted architectures: ${ARCHS}" lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}" rm "${EXTRACTED_ARCHS[@]}" echo "Replacing original executable with thinned version" rm "$FRAMEWORK_EXECUTABLE_PATH" mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH" done
Install the Mobile SDK for iOS Config File
Click the [Config File] button on the mobile app's Details tab to download the MPConfig.plist file to your desktop. Add it to the top-level of your project.
The configuration file will include the system-generated AppKey, AppID, AccountID and an inProduction boolean value, and look something similar to below:
Choose the option when adding the .plist file
NOTE: If you do not wish to add the MPConfig.plist to your project, you'll need to initiate the SDK by setting the required values manually.
Swift:
MPPush.shared.startup(withApplicationKey: "your_app_key", withAppId: "your_app_id", withAccountId: "your_account_id", forProduction: false)
Objective C:
[[MPPush shared]startupWithApplicationKey:@"your_app_key" withAppId:@"your_app_id" withAccountId:@"your_account_id" forProduction:NO];
where your_app_key, your_app_id and your_account_id are found in the following places
Finally, you’ll need to create a new API authentication token for use by the mobile SDK. This is an additional layer of required security. Read more about creating API authentication tokens. When you create the authentication token, make sure that you’ve given it privileges for Push Notifications.
Import the required header files:
Swift
Create YourApp-Bridging-Header.h and import the MPPush header file as
#import <MPPush/MPPush.h>
Objective C
In your AppDelegate.m file, include the following:
#import <MPPush/MPPush.h>
Start up MPPush services
The MPPush SDK requires only a single entry point in the app delegate, which is startup:
. In your AppDeleget.m file,
- Locate the
application:didFinishLaunchingWithOptions:
method. - Initialize a shared MPPush instance by calling
[MPPush startup];
. - Set preferences before calling
startup:
and provide device data or in-app message customization after callingstartup:
.
Swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. // Tell the SDK your preferences BEFORE startup // For example, set your preferences for the types of notifications your app should receive: MPPush.shared.setMPUserNotificationTypes([MPUserNotificationType.alert, MPUserNotificationType.badge ]) // Toggle whether the SDK logs progress (YES by default): MPPush.setLogging(true) // Toggle whether the SDK tracks users by location (NO by default) MPPush.shared.trackLocation(shouldTrack: true)// Will be required for location based segments // Toggle whether the SDK should auto display In-App Messages (YES by default): MPPush.shared.shouldAutoDisplayInAppMessages(shouldAutoDisplay: true) // Number of seconds the SDK will wait to auto-display pending in-app messages after app launch; 3 seconds by default MPPush.shared.setInAppDisplayDelay(second: 0) MPPush.shared.startup(withAuthToken: "your_api_auth_token") // OR, if you don't wish to include MPConfig.plist, do: // MPPush.shared.startup(withApplicationKey: "your_app_key", withAppId: "your_app_id", withAccountId: "your_account_id", withAuthToken: "your_api_auth_token", forProduction: true) return true }
Additional Methods
All the following methods can be called anywhere in your application but MUST be called AFTER startup:
, or startupWithApplicationKey:withAppId:withAccountId:forProduction:
MPPush.shared.setUserLoginID("1234")
-- Link device with the user's login ID.MPPush.shared.setUserFirstName("John", lastName: "Appleseed")
-- Set the device user's full name (if available).MPPush.shared.setUserEmailID("john@example.com")
-- Link device with an email ID (if available) for domain based segmentation.MPPush.shared.addUserTag("tagToAdd")
-- Add a tag to current device for segmentationMPPush.shared.removeUserTag("tagToRemove")
-- Remove a tag for current device.MPPush.shared.setInAppFont(inAppFont: myFontFamily)
-- Set the desired font family to use when displaying in-app messages.MPPush.shared.setPrimaryInAppColor(primaryInAppColor: myTextColor)
-- Set the desired primary color(text color in Hex code) to use when displaying in-app messages.MPPush.shared.setSecondaryInAppColor(secondaryInAppColor: myBackgroundColor)
-- Set the desired secondary color(background color in Hex code) to use when displaying in-app messages.MPPush.shared.displayPendingInAppMessages()
-- Manually invoke when to display in-app messages in case auto-display is turned off by [MPPush setInAppAutoDisplayMode:]
Objective C
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { // Override point for customization after application launch. // Tell the SDK your preferences BEFORE startup // For example, set your preferences for the types of notifications your app should receive: [[MPPush shared] setMPUserNotificationTypesWithTypes:UIUserNotificationTypeAlert]; // Toggle whether the SDK logs progress (YES by default): [[MPPush shared] setLoggingWithValue:YES]; // Toggle whether the SDK tracks users by location (NO by default): [[MPPush shared] trackLocationWithShouldTrack:YES]; // Will be required for location based segments // Toggle whether the SDK should auto display In-App Messages (YES by default): [[MPPush shared] shouldAutoDisplayInAppMessagesWithShouldAutoDisplay:YES]; // Number of seconds the SDK will wait to auto-display pending in-app messages after app launch [[MPPush shared] setInAppDisplayDelayWithSeconds:0]; // (3 seconds by default) [[MPPush shared] startupWithAuthToken:@"your_api_auth_token"]; // OR, if you don't wish to include MPConfig.plist, do: // [MPPush startupWithApplicationKey:@"your_app_key" withAppId:@"your_app_id" withAccountId:@"your_account_id" withAuthToken:@"your_api_auth_token" forProduction: YES]; // ******************************************************// return YES; }
All the following API methods can be called anywhere in your application but MUST be called AFTER startup:
or startupWithApplicationKey:withAppId:withAccountId:forProduction:
[[MPPush shared] setUserLoginID:@"1234"];
-- Link device with the user's login ID.[{MPPush shared] setUserFirstName:@"John" lastName:@"Appleseed"];
-- Set the device user's full name (if available).[[MPPush shared] setUserEmailID:@“ravi@maropost.com”];
-- Link device with an email ID (if available) for domain based segmentation.[[MPPush shared] addUserTag:@"tagToAdd"];
-- Add a tag to current device for segmentation.[[MPPush shared] removeUserTag:@"tagToRemove"];
-- Remove a tag for current device.[[MPPush shared] setInAppFont:myFont];
-- Set the desired font family to use when displaying in-app messages.[[MPPush shared] setPrimaryInAppColor:myTextColor];
-- Set the desired primary color(text color in Hex code) to use when displaying in-app messages.[[MPPush shared] setSecondaryInAppColor:myBackgroundColor"];
-- Set the desired secondary color(background color in hex code) to use when displaying in-app messages.[[MPPush shared] displayPendingInAppMessages”];
-- Manually invoke when to display in-app messages in case auto-display is turned off by [[MPPush shared] setInAppAutoDisplayMode:]
Make sure to use the same bundle identifier in your app and in the APNS certificate. This error is the usual reason for devices not receiving push messages sent from Maropost for Marketing. Finally, build your project and you're all set.
Deep Linking From a Push Notification
Deep linking adds an additional user experience to push notifications. Normally when a mobile app user clicks a push notification, the mobile app launches and the app's home screen appears. Deep linking will not only launch the mobile app, it will also display the specified screen within the app. In this section, you will learn how to enable deep linking within an app, and how to configure a deep link URL for a specific screen in the app.
Create an App and Enable Deep Linking
Create a basic app in XCode (v8.0.0 or above) consisting of a main ViewController pushed on a UINavigation ViewController. Also, create some additional ViewControllers to be used later. To enable deep linking, go to the Info tab in the XCode project. In the 'URL Types' section, click on the + button and then add an identifier and a URL scheme. Ensure that the identifier and URL scheme you select are unique to the mobile app.
Remember to take note of the URL scheme you enter, as this is how iOS knows to open a screen in your app. For example, Maropost's own mobile app for iOS uses maropost:// as its URL scheme. To confirm that your URL scheme has been registered, check Info.plist for an entry named 'URL Types'. Expand the section to see the new URL scheme you have just registered. You can verify that this scheme is working by typing the following URL into the Safari browser on your mobile device: your_url_scheme:// (e.g. maropost://) This should open up your mobile app. The format of a deep link URL is [scheme]://[host]/[path] where:
- scheme - The URL scheme that you registered. It tells iOS which app to launch. Note that the URL scheme should be registered with the device for it to recognize the link.
- host - The host is analogous to a website/server name on the web. You can handle multiple hosts within a single mobile app.
- path - Enables you to provide additional information regarding the location of the screen within the mobile app.
Handling the Opening of Registered URLs Within Your App
Once you have confirmed that your URL scheme is registered and the deep linking is working, you will not handle the URL used to launch the mobile app. In its present state, your app can be launched using a simple URL, but not much beyond that will happen. To accomplish true deep linking, you will need to add and override the following function in AppDelegate:
Swift
func application(_ app: UIApplication, open url: url, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
Objective C
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
Where:
- url - The complete URL used to launch the app and display the specific screen of the app.
- sourceApplication - The bundle ID for the application from which the URL was called.
- annotation - A property list object that can be used to pass additional information along with the URL.
This function is not present by default; you will need to add it in. This function gets called each time the mobile app launches using the registered URL scheme. The exact implementation of the method depends upon your needs. In this example below, the method simply checks the host and then based on the path, loads a particular ViewController.
Swift
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { print("Schema:\(url)"); print("Schema Url Path:\(url.path)"); print("Schema Url host:\(url.host!)"); let urlPath: String = url.path as String! if (urlPath == "/inner"){ print("Call Inner Pages”) } else { return false } return true }
Objective C
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{ if([[url host] isEqualToString:@"page"]){ if([[url path] isEqualToString:@"/page1"]){ [self.mainController pushViewController:[[Page1ViewController alloc] init] animated:YES]; } else{ return NO; } return YES; }
Implement Rich Push Messages using the NotificationService extension
Rich push messaging uses the UNNotificationServiceExtension class to create an object that modifies the content of a remote notification before it is delivered to the end user's mobile app. The UNNotificationServiceExtension class provides the entry point for a Notification Service app extension, which lets you customize the content of a remote notification before it is delivered to the user. A Notification Service app extension does not present any UI of its own. Instead, it is launched on demand when a notification of the appropriate type is delivered to the user’s device. You use this extension to modify the notification’s content or download content related to the extension.
For example, you could use the extension to decrypt an encrypted data block or to download images associated with the notification. You do not create instances of the UNNotificationServiceExtension class yourself. Instead, the Xcode template for a Notification Service Extension target contains a subclass for you to modify. Use the methods of that subclass to implement your app extension’s behavior. When a remote notification for your app is received, the system loads your extension and calls its didReceive(_:withContentHandler:) method only when both of the following conditions are met:
- The remote notification is configured to display an alert.
- The remote notification’s app dictionary includes the mutable-content key with the value set to 1.
NOTE: You cannot modify silent notifications or those that only play a sound or badge the app’s icon. The didReceive(_:withContentHandler:) method performs the main work of your extension. You use that method to make any changes to the notification’s content. That method has a limited amount of time to perform its task and execute the provided completion block. If your method does not finish in time, the system calls the serviceExtensionTimeWillExpire() method to give you one last chance to submit your changes. If you do not update the notification content before time expires, the system displays the original content. As for any app extension, you deliver a Notification Service app extension class as a bundle inside your app. The template provided by Xcode configures the Info.plist file automatically for this app extension type. Specifically, it sets the value of the NSExtensionPointIdentifier key to com.apple.usernotifications.service and sets the value of the NSExtensionPrincipalClass key to the name of your UNNotificationServiceExtension subclass. For information about how to create and schedule remote notifications using the Apple Push Notification service (APNS), see Local and Remote Notification Programming Guide. The Xcode templates provide a subclass of UNNotificationServiceExtension for you to modify. You must implement the didReceive(_:withContentHandler:) method and use it to process incoming notifications. It is also strongly recommended that you override the serviceExtensionTimeWillExpire() method.
Swift
func didReceive(UNNotificationRequest, withContentHandler: (UNNotificationContent) -> Void)
This method is called when there is a notification ready to be modified.
func serviceExtensionTimeWillExpire()
This is method is called when an extension is about to be terminated.
Objective C
- didReceiveNotificationRequest:withContentHandler:
This method is called when there is a notification ready to be modified.
- serviceExtensionTimeWillExpire
This method is called when an extension is about to be terminated.
Parsing the Notification Payload
Swift
class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? var downloadTask: URLSessionDownloadTask? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { func failEarly() { contentHandler(request.content) } self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) // Get the custom data from the notification payload if let data = request.content.userInfo as? [String: AnyObject] { // Grab the attachment // let notificationData = data["data"] as? [String: String] if let urlString = data["attachment-url"], let fileUrl = URL(string: urlString as! String) { // Download the attachment URLSession.shared.downloadTask(with: fileUrl) { (location, response, error) in if let location = location { // Move temporary file to remove .tmp extension let tmpDirectory = NSTemporaryDirectory() let tmpFile = "file://".appending(tmpDirectory).appending(fileUrl.lastPathComponent) let tmpUrl = URL(string: tmpFile)! try! FileManager.default.moveItem(at: location, to: tmpUrl) // Add the attachment to the notification content if let attachment = try? UNNotificationAttachment(identifier: "video", url: tmpUrl, options:nil) { self.bestAttemptContent?.attachments = [attachment] }else if let attachment = try? UNNotificationAttachment(identifier: "image", url: tmpUrl, options:nil) { self.bestAttemptContent?.attachments = [attachment] }else if let attachment = try? UNNotificationAttachment(identifier: "audio", url: tmpUrl, options:nil) { self.bestAttemptContent?.attachments = [attachment] }else if let attachment = try? UNNotificationAttachment(identifier: "image.gif", url: tmpUrl, options: nil) { self.bestAttemptContent?.attachments = [attachment] } } // Serve the notification content self.contentHandler!(self.bestAttemptContent!) }.resume() } } } }
Objective C
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; // Modify the notification content here... // self.bestAttemptContent.body = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.body]; // check for media attachment, example here uses custom payload keys mediaUrl and mediaType NSDictionary *userInfo = request.content.userInfo; if (userInfo == nil) { [self contentComplete]; return; } if ([userInfo objectForKey:@"attachment-url"]) { [self loadAttachmentForUrlString:[userInfo objectForKey:@"attachment-url"] completionHandler: ^(UNNotificationAttachment *attachment) { self.bestAttemptContent.attachments = [NSArray arrayWithObjects:attachment, nil]; }]; } }
In this protocol, [didReceiveNotificationRequest] the mobile app accesses the userInfo and starts downloading the image from the image URL using NSURL session. Once the image is downloaded the UNNotificationAttachment object is initialized with local downloaded path and the attachment object is set to the notification object. The custom method which will download the image in the local directory is as follows:
- (void)loadAttachmentForUrlString:(NSString *)urlString completionHandler:(void (^)(UNNotificationAttachment *))completionHandler { __block UNNotificationAttachment *attachment = nil; __block NSURL *attachmentURL = [NSURL URLWithString:urlString]; NSString *fileExt = [@"." stringByAppendingString:[urlString pathExtension]]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDownloadTask *task = [session downloadTaskWithURL:attachmentURL completionHandler: ^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) { if (error != nil) { NSLog(@"%@", error.localizedDescription); } else { NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExt]]; [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error]; NSError *attachmentError = nil; attachment = [UNNotificationAttachment attachmentWithIdentifier:[attachmentURL lastPathComponent] URL:localURL options:nil error:&attachmentError]; if (attachmentError) { NSLog(@"%@", attachmentError.localizedDescription); } } completionHandler(attachment); }]; [task resume]; }
At this point the process of downloading the image is complete, and the image is set to the attachment object. Next, handle the return completion handler once the processing is done or when the time will expire protocol gets called.
- (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. [self contentComplete]; } - (void)contentComplete { [self.session invalidateAndCancel]; self.contentHandler(self.bestAttemptContent); }
Build, run and send a test notification. You can use pusher service to test this quickly. Refer to the sample service extension payload example.