• Dashboard
  • API
  • Guides
  • FAQ
  • iOS > Advanced

    Intercepting notifications

    Intercepting the custom payload of an Batch iOS push is straightforward: You just have to keep using the standard application delegate methods, everything will work automatically.

    iOS 10

    It's recommended to implement the UNUserNotificationCenterDelegate in a class.
    It allows you to:

    • Unify local and remote notifications callbacks
    • Get the same callback for all user interactions with a push (open, dismiss and actions), even for a cold start
    • Get notified of notifications received while your app is in the foreground. This allows you to take immediate action or to tell iOS to display the push as if your app was opened in background.

    In iOS 10.0, legacy callbacks still work but they are heavily bugged and differ in behaviour from how they used to work. We suggest you migrate to UNUserNotificationCenter as soon as possible. More info here: http://www.openradar.me/27935982.

    You'll also need to call Batch in its two methods to make sure all of the SDK and dashboard's features work correctly.
    Keep in mind that being a delegate, it will need to be retained by a variable, since iOS only weakly retains it.

    Here's a sample implementation:

    // NotificationDelegate.h
    
    @import Foundation;
    @import UserNotifications;
    
    @interface NotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
    
    @end
    
    // NotificationDelegate.m
    #import "NotificationDelegate.h"
    
    @import Batch;
    
    @implementation NotificationDelegate
    
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center
    didReceiveNotificationResponse:(UNNotificationResponse *)response
             withCompletionHandler:(void (^)())completionHandler {
        [your code]
        [BatchPush handleUserNotificationCenter:center
                 didReceiveNotificationResponse:response];
        completionHandler();
    }
    
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center
           willPresentNotification:(UNNotification *)notification
             withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
        [your code]
        [BatchPush handleUserNotificationCenter:center
                        willPresentNotification:notification
                  willShowSystemForegroundAlert:YES];
    
        // Since you set willShowSystemForegroundAlert to true, you should call completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound)
        // If set to false, you'd call completionHandler(0)
    }
    
    @end
    @objc
    class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
        func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
            [your code]
            BatchPush.handle(userNotificationCenter: center, didReceive: response)
        }
    
        func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
            [your code]
            BatchPush.handle(userNotificationCenter: center, willPresent: notification, willShowSystemForegroundAlert: true)
    
            // Since you set willShowSystemForegroundAlert to true, you should call completionHandler([.alert, .sound, .badge])
            // If set to false, you'd call completionHandler([])
        }
    }

    Note: willShowSystemForegroundAlert should reflect the arguments you call the completion handler with. Batch will use that to detect if it should perform foreground actions, or only perform them when the shown alert will be tapped

    Finally, set this class as your default UNUserNotificationCenter delegate:

    @interface AppDelegate ()
    {
        NotificationDelegate *notificationDelegate;
    }
    @end
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        if ([UNUserNotificationCenter class]) {
            notificationDelegate = [NotificationCenterDelegate new];
            [[UNUserNotificationCenter currentNotificationCenter] setDelegate:notificationDelegate];
        }
    }
    
    @end
    class AppDelegate: UIResponder, UIApplicationDelegate {
        let notificationDelegate = NotificationDelegate()
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            if #available(iOS 10, *) {
                UNUserNotificationCenter.current().delegate = notificationDelegate
            }
        }
    }

    Handling a custom payload

    The custom payload is merged at the root of the userInfo you get when called back by iOS:

    // Works with both methods
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center
    didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
    {
    
      NSDictionary *userInfo = response.notification.request.content.userInfo
    
      // When reading the custom payload, you should perform extensive type checking to prevent crashes if you
      // ever change a type, be it intentionally or accidentally.
    
      // Root keys will be of the type you used in your source JSON, unlike Android.
      // Please see NSJSONSerialization for more info about how the JSON will be interpreted by iOS
      // Here we'll read the "article_id" key of the following custom payload : {"article_id": 2}
      NSNumber *articleID = userInfo[@"article_id"];
      if ([articleID isKindOfClass:[NSNumber class]]) {
        // Handle your article ID
      }
    
      // If you have more complex objets, they also should be parsed correctly
      // Matching custom payload: {"user_data": {"id": 2}}
      NSDictionary *userData = userInfo[@"user_data"];
      if ([userData isKindOfClass:[NSDictionary class]]) {
        NSNumber userID = userData[@"id"];
        if ([userID isKindOfClass:[NSNumber class]]) {
          // Handle your user ID
        }
      }
    }
    // Works with both methods
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
    
      let userInfo = response.notification.request.content.userInfo
    
      // When reading the custom payload, you should perform extensive type checking to prevent crashes if you
      // ever change a type, be it intentionally or accidentally.
    
      // Root keys will be of the type you used in your source JSON, unlike Android.
      // Please see NSJSONSerialization for more info about how the JSON will be interpreted by iOS
      // Here we'll read the "article_id" key of the following custom payload : {"article_id": 2}
      if let articleID = userInfo["article_id"] as? Int {
        // Handle your article ID
      }
    
      // If you have more complex objets, they also should be parsed correctly
      // Matching custom payload: {"user_data": {"id": 2}}
      if let userData = userInfo["user_data"] as? [AnyHashable : Any],
          let userID = userData["id"] as? Int {
          // Handle your user ID
      }
    }

    iOS 9 and lower

    If your app supports background refresh, it's recommended to implement this method:

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
           func application(application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable : Any],
           fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)

    Don't forget to call the completionHandler with an appropriate value. Batch will only do so for you when it encounters a deeplink.

    Otherwise, if you support iOS 6 devices or don't implement background refresh, please implement the older notification delegate method. Note that both can be implemented for retrocompatbility, you'll only be called on one of them.

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any])

    Your custom payload will be in the userInfo dictionary.

    You'll notice the presence of a com.batch key in the userInfo dictionary. This is Batch's internal payload: it is subject to change so you should not rely on parsing it for any of your features.

    Handling a custom payload

    The custom payload is merged at the root of the userInfo you get when called back by iOS:

    // Works with both methods
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    {
      // When reading the custom payload, you should perform extensive type checking to prevent crashes if you
      // ever change a type, be it intentionally or accidentally.
    
      // Root keys will be of the type you used in your source JSON, unlike Android.
      // Please see NSJSONSerialization for more info about how the JSON will be interpreted by iOS
      // Here we'll read the "article_id" key of the following custom payload : {"article_id": 2}
      NSNumber *articleID = userInfo[@"article_id"];
      if ([articleID isKindOfClass:[NSNumber class]]) {
        // Handle your article ID
      }
    
      // If you have more complex objets, they also should be parsed correctly
      // Matching custom payload: {"user_data": {"id": 2}}
      NSDictionary *userData = userInfo[@"user_data"];
      if ([userData isKindOfClass:[NSDictionary class]]) {
        NSNumber userID = userData[@"id"];
        if ([userID isKindOfClass:[NSNumber class]]) {
          // Handle your user ID
        }
      }
    }
    // Works with both methods
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
      // When reading the custom payload, you should perform extensive type checking to prevent crashes if you
      // ever change a type, be it intentionally or accidentally.
    
      // Root keys will be of the type you used in your source JSON, unlike Android.
      // Please see NSJSONSerialization for more info about how the JSON will be interpreted by iOS
      // Here we'll read the "article_id" key of the following custom payload : {"article_id": 2}
      if let articleID = userInfo["article_id"] as? Int {
        // Handle your article ID
      }
    
      // If you have more complex objets, they also should be parsed correctly
      // Matching custom payload: {"user_data": {"id": 2}}
      if let userData = userInfo["user_data"] as? [AnyHashable : Any],
          let userID = userData["id"] as? Int {
          // Handle your user ID
      }
    }

    Overriding Batch's Deeplink Handling

    By default, Batch will automatically try to open the deeplink you've set in your push campaigns.

    If you'd like to prevent Batch from doing that while still being able to use deeplinks in push campaigns, you can call the following method in applicationDidFinishLaunchingWithOptions:

    [BatchPush enableAutomaticDeeplinkHandling:NO];
    BatchPush.enableAutomaticDeeplinkHandling(false)

    Then, you can ask Batch to give you the deeplink from the notification when you get it:

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
        NSString *deeplink = [BatchPush deeplinkFromUserInfo:userInfo];
    }
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
        let deeplink = BatchPush.deeplink(fromUserInfo: userInfo)
    }

    Be careful, this method will return nil if a deeplink could not be found.