Mobile landings
Mobile Landings allow you to easily introduce continuity between your app, and your pushes: A user opening a push will be greeted by a rich message related to what they opened, rather than just ending up on your app's main menu.
They're included in the Startup, Business and Enterprise plans.
Displaying the message
Automatic mode
There's no code required to make mobile landings work in automatic mode: just attach a landing to your push campaign, and Batch will display it.
You might want to go further into this documentation, and setup your delegate, or head to the Custom Actions documentation to add custom behaviour to buttons.
Manual mode
You may want to be in control of if, when and how landings will be loaded and displayed. Batch allows you to disable automatic displaying, and handle loading and displaying the view controller itself.
First, you'll need to implement UNUserNotificationCenterDelegate
in a class (for more information, please see the Intercepting notifications
part):
- Swift
- Objective-C
@objc
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
AppDelegate.tryShowBatchMessage(userInfo: response.notification.request.content.userInfo as [NSObject : AnyObject])
BatchPush.handle(userNotificationCenter: center, didReceive: response)
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
AppDelegate.tryShowBatchMessage(userInfo: response.notification.request.content.userInfo as [NSObject : AnyObject])
BatchPush.handle(userNotificationCenter: center, willPresent: notification, willShowSystemForegroundAlert: true)
completionHandler([.sound, .alert, .badge])
}
}
Then, you have to disable the automatic mode and set your class as your default UNUserNotificationCenter
delegate:
- Swift
- Objective-C
class AppDelegate: UIResponder, UIApplicationDelegate {
let notificationDelegate = NotificationDelegate()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
BatchMessaging.setAutomaticMode(on: false)
UNUserNotificationCenter.current().delegate = notificationDelegate
}
}
Finally, you need to ask Batch to load the right view controller for the push payload (if applicable), and display it:
- Swift
- Objective-C
// AppDelegate.swift
func tryShowBatchMessage(userInfo: [NSObject : AnyObject]) {
// Put the display code in this if block if you don't want the messaging to show when the user is already using the app
// if UIApplication.shared.applicationState == .inactive {
// }
guard let message = BatchMessaging.message(fromPushPayload: userInfo) else {
// No message from push payload
return
}
guard let vc = try? BatchMessaging.loadViewController(for:message) else {
print("An error occurred while loading Batch's messaging view")
return
}
// Let Batch present it itself
BatchMessaging.present(vc)
// Or,
// if you want to display the message yourself, you will need to check if the VC should be presented in
// its own window, to allow user interaction around the message if needed. Banners require this, for example.
guard let batchVC = vc as? BatchMessagingViewController else {
print("The loaded VC does not satisfy the BatchMessagingViewController protocol like it should")
return
}
if (batchVC.shouldDisplayInSeparateWindow) {
// Display in your own UIWindow
} else {
var targetVC = UIApplication.shared.keyWindow?.rootViewController
if let presentedVC = targetVC?.presentedViewController {
targetVC = presentedVC
}
targetVC?.present(vc, animated: true, completion: nil)
}
}
Controlling the display using "Do Not Disturb mode"
Batch 1.10 adds a "Do Not Disturb" (DnD) feature: It allows you to tell Batch to hold on a mobile landing for you, rather than display it without using the fully manual mode.
For example, if launching your app results in a splash screen or a fullscreen ad, you might find it undesirable to have Batch display something on top of it.
Turning on "Do Not Disturb" mode will make Batch enqueue the latest mobile landing, rather than display it.
Toggling DnD
Now, when you don't want Batch to automatically display, turn on Do Not Disturb:
- Swift
- Objective-C
BatchMessaging.doNotDisturb = true
Once you want to start showing landings automatically, call the method with false
to turn it off.
Note: Disabling Do Not Disturb mode does NOT make Batch show the enqueued message
Displaying pending mobile landings
After coming back from DnD mode, you might want to show the enqueued message, as Batch will not do that automatically. Batch exposes two properties/methods for managing the queue:
BatchMessaging.hasPendingMessage
, allowing you to peek into the queue.BatchMessaging.popPendingMessage()
, allowing you to fetch the pending message (if any). Since calling this makes Batch delete its reference to it to save memory, further calls might returnnil
.BatchMessaging.showPendingMessage()
, allowing you to try to show the pending message, if any.
Here is a quick example of how they can be used:
- Swift
- Objective-C
func splashScreenDidDisappear() {
BatchMessaging.doNotDisturb = false
BatchMessaging.showPendingMessage()
}
Note: Only the latest message is queued: if a mobile landing arrives while one is still pending, it will overwrite the previous one.
Listening to lifecycle events and reacting to button actions
Setting up a delegate
Batch's messaging module supports setting up a delegate, which can be used for analytics:
It can be any object that implements the BatchMessagingDelegate protocol.
While your application delegate can safely implement this protocol, we split it out in a separate class in our examples for simplicity.
- Swift
- Objective-C
var messagingDelegate: SampleBatchMessagingDelegate?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
[...]
messagingDelegate = SampleBatchMessagingDelegate()
BatchMessaging.setDelegate(messagingDelegate!)
[...]
}
Like most delegates on iOS, Batch only stores a weak reference to it. Make sure you keep a reference to your object instance so it isn't released
Analytics delegate
Batch can notify your delegate of lifecycle events of the in-app messages:
The
messageIdentifier
variable is the message tracking identifier you've configured in the dashboard. It can be nil if you didn't specify one.
- Swift
- Objective-C
import Foundation
import Batch.Messaging
public class SampleBatchMessagingDelegate: NSObject, BatchMessagingDelegate {
public func batchMessageDidAppear(messageIdentifier: String?) {
print("SampleBatchMessagingDelegate - batchMessageDidAppear: \(messageIdentifier)")
}
public func batchMessageDidDisappear(messageIdentifier: String?) {
print("SampleBatchMessagingDelegate - batchMessageDidDisappear: \(messageIdentifier)")
}
}
Since Batch 1.14, additional callbacks have been added to:
- Track when a button has been pressed, along with the action that goes with it
- Track when a message has been cancelled (close button/swipe to dismiss)
- Track when a message has been automatically closed due to the timer running out
To use them, simply implement the methods you need:
- Swift
- Objective-C
import Foundation
import Batch.Messaging
public class SampleBatchMessagingDelegate: NSObject, BatchMessagingDelegate {
public func batchMessageDidTriggerAction(_ action: BatchMessageAction, messageIdentifier identifier: String?, actionIndex index: Int) {
// A button or global tap action has been triggered
// On templates that support a global tap action (like Image), the index will be a special value
if index == BatchMessageGlobalActionIndex {
// It was the global tap action
} else if let cta = action as? BatchMessageCTA {
let label = cta.label
// [...]
}
// CTAs are allowed to simply be a "dismiss" action, in which case they
// will not have any associated action name and arguments
if !action.isDismissAction() {
let actionName = action.action // not null
// [...]
}
}
public func batchMessageWasCancelledByAutoclose(_ messageIdentifier: String?) {
// Message has been automatically closed after a delay
}
public func batchMessageWasCancelledByUserAction(_ messageIdentifier: String?) {
// Message has been cancelled by either
// - Tapping the close button
// - A swipe to dismiss
}
}
Custom button actions
In order to be able to use the "Custom" button action kind, you need to implement them using the Batch Actions module. More info here: Custom Actions
Customizing the landing
Setting a custom font
If you'd like to use a custom font instead of the system's, Batch allows you to override the fonts it will use:
- Swift
- Objective-C
// Set a custom font
let font = UIFont(name: "MyFont", size: 10)
let boldFont = UIFont(name: "MyFont-Bold", size: 10)
BatchMessaging.setFontOverride(font, boldFont: boldFont)
// Clear the custom font, and use the system one
BatchMessaging.setFontOverride(nil, boldFont: nil)
The size will be overriden later, so you can use anything you want. Make sure you provide both a normal and a bold font, even if they are the same.
This assumes you've already got custom UIFonts working. If you don't, you can find a great tutorial here.
Troubleshooting
Nothing happens when I press an actionable button
Take a look at your application logs in Xcode, the SDK might try to warn you about an issue. Here are some the common messages and their probable cause:
An error occured while making a NSURL for the following link: '<your deeplink>', ignoring deeplink action.
Deeplinks on iOS are automatically called by the SDK using sharedApplication's openURL method. Since it needs a NSURL instance, the deeplink string needs to be a valid URL accepted by iOS' NSURL class. Please try again with a valid URL.
Note: Use of universal links in deeplinks is discouraged: triggering an universal link from the app implementing them will cause iOS to open safari.
The action 'ACTION NAME' couldn't be found. Did you forget to register it?
This can happen when you specified a custom action when creating the campaign on the dashboard, but the SDK couldn't execute it.
Make sure you always register your actions at every app start.