Documentation > iOS

Mobile landings

Batch 1.7 introduces 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 Premier and Enterprise plans.

Mobile landings visual example

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 disable the automatic mode:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [...]

  [BatchMessaging setAutomaticMode:NO];

  [...]
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  [...]

  BatchMessaging.setAutomaticMode(false)

  [...]
}

Then, you need to ask Batch to load the right view controller for the push payload (if applicable), and display it:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...

    [self tryShowBatchMessage:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    [self tryShowBatchMessage:userInfo];
}

- (void)tryShowBatchMessage:(NSDictionary *)userInfo
{
    if (!userInfo) {
        return;
    }
    // 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 sharedApplication] applicationState] == UIApplicationStateInactive) {
    }
    */

    BatchMessage *message = [BatchMessaging messageFromPushPayload:userInfo];
    if (message) {
        // You can show a loading view in the meantime for a better user experience
        NSError *err = nil;
        UIViewController *vc = [BatchMessaging loadViewControllerForMessage:message error:&err];

        if (err) {
            NSLog(@"An error occurred while loading Batch's messaging view: %@", [err localizedDescription]);
        } else if (vc) {
            UIViewController *targetVC = [[[UIApplication sharedApplication] keyWindow] rootViewController];
            UIViewController *presentedVC = targetVC.presentedViewController;
            if (presentedVC) {
                targetVC = presentedVC;
            }
            [targetVC presentViewController:vc animated:YES completion:nil];
        }
    }
}

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    ...

    if let notificationPayload = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? [NSObject : AnyObject] {
        tryShowBatchMessage(notificationPayload)
    }
}

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    tryShowBatchMessage(userInfo)
}

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.sharedApplication().applicationState == .Inactive {
    }
    */

    if let message = BatchMessaging.messageFromPushPayload(userInfo) {
       // You can show a loading view in the meantime for a better user experience

       do {
           let vc = try BatchMessaging.loadViewControllerForMessage(message)
           var targetVC = UIApplication.sharedApplication().keyWindow?.rootViewController
           if let presentedVC = targetVC?.presentedViewController {
               targetVC = presentedVC
           }
           targetVC?.presentViewController(vc, animated: true, completion: nil)
       } catch let error as NSError {
           print("An error occurred while loading Batch's messaging view: \(error.localizedDescription)")
       } catch {
           print("An unknown error occurred while loading Batch's messaging view")
       }
    }
}

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.

Note: While your application delegate can safely implement this protocol, we split it out in a separate class in our examples for simplicity.

// Header file

@property (strong, nonatomic) SampleBatchMessagingDelegate *messagingDelegate;

// Implementation

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [...]

    // Delegates are weak so be sure to keep a reference to it
    self.messagingDelegate = [SampleBatchMessagingDelegate new];
    [BatchMessaging setDelegate:self.messagingDelegate];

    [...]
}

var messagingDelegate: SampleBatchMessagingDelegate?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  [...]

  messagingDelegate = SampleBatchMessagingDelegate()
  BatchMessaging.setDelegate(messagingDelegate!)

  [...]
}

Note: 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:

Note: The messageIdentifier variable is the message tracking identifier you've configured in the dashboard. It can be nil if you didn't specify one.
For the default cancel button, buttonIdentifier will be equal to the kBatchMessagingCloseButtonTrackingIdentifier constant.

// Header file (.h)
@import Foundation;
@import Batch.Messaging;

@interface SampleBatchMessagingDelegate : NSObject <BatchMessagingDelegate>

@end

// Implementation file (.m)

#import "SampleBatchMessagingDelegate.h"

@implementation SampleBatchMessagingDelegate

- (void)batchMessageDidAppear:(NSString* _Nullable)messageIdentifier
{
    NSLog(@"SampleBatchMessagingDelegate - batchMessageDidAppear: %@", messageIdentifier);
}

- (void)batchMessageDidDisappear:(NSString* _Nullable)messageIdentifier
{
    NSLog(@"SampleBatchMessagingDelegate - batchMessageDidDisappear: %@", messageIdentifier);
}

@end
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)")
    }

}

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:


// Set a custom font

UIFont* font = [UIFont fontWithName:@"MyFont" size: 10];
UIFont* boldFont = [UIFont fontWithName:@"MyFont-Bold" size: 10];
[BatchMessaging setFontOverride:font boldFont:boldFont];

// Clear the custom font, and use the system one
[BatchMessaging setFontOverride:nil boldFont:nil];
// 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.

Note: This assumes you've already got custom UIFonts working. If you didn'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.