All pages
Powered by GitBook
1 of 12

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Advanced

Customizing notifications

Batch allows you to customize your notifications adding specific keys to the payload.

Since we allow the overriding of Apple's keys, you can use and override anything Apple accepts in the aps key. Apple's full documentation about the payload is located here

All of the following JSON examples go into the custom_payload field of both Transactional and Campaigns APIs.

Adding a badge

You can add a badge to your iOS notifications by adding a badge key to the payload of your push. Here is how it looks:

You should reset the badge count when the app is opened.

Adding actions buttons

You can add action buttons to your notification by adding a category key to the payload of your push. Here is how it looks:

We recommend you follow to register categories.

Choosing a notification sound

You can set a custom sound for your notification by adding the sound key to your payload. All you need to do is to add your custom sound to your app bundle before choosing it. Here is how it looks:

You can't remotely push new sounds to your app. The filename (or "default") must be a sound file already present in your app bundle. If it is missing, iOS will play the default sound.

Triggering a background refresh

iOS (7+) supports sending pushes that will wake up your app in the background. It works like background fetching, except that you control when your app will wake up.

This allows you to fetch new content (news feed, conversation list, etc) so your users won't have to wait for your app to sync when then open it.

First, you'll need to go in Xcode and enable "Remote notifications" as a Background Mode in your app's Capabilities:

Then, add "content-available":1 in your aps object:

Users can disable background refresh for your application, or the system might not deliver it in some cases (like battery saving mode).You shouldn't rely on this to work at all times: always fetch data that you can fetch in a more reliable way when your app starts.

Sending silent notifications

Batch supports background notifications, which allow you to run actions for a limited amount of time without notifying the user.

Please keep in mind that the same restrictions apply as the ones described higher up for Background Refresh.

When calling our Campaign/Transactional APIs, add 'push_type': 'background' to the request's JSON body, and remove the message/messages/media keys.

Silent notifications are not supported from the dashboard yet. Using the legacy way to do silent notifications using only the custom payload will NOT work on iOS 13 and later.

As said earlier for background refresh, we advise against triggering local notifications in response to a silent notification. While they are handy, the delivery rate will suffer heavily due to the unreliableness of background refresh.

Disabling foreground notifications

Since Batch 2.0 the SDK always display notification while the App is in foreground. You can change this behaviour as following:

If you registered BatchUNUserNotificationCenterDelegate as your delegate, use the showForegroundNotifications property.

You can also do so with your own UNUserNotificationCenterDelegate:

  • Create a class that implements UNUserNotificationCenterDelegate

  • Override userNotificationCenter(center:willPresentNotification:withCompletionHandler) with willShowSystemForegroundAlert to false and call completionHandler([]).

  • Call Batch's appropriate methods so you don't break any features

Additional notification settings

iOS allows your app to show a button to configure notifications when the user long presses a notification.

You can enable this by calling this API:

Note that this still requires you to implement a UNUserNotificationCenterDelegate, and the appropriate method to open the settings.

Set it as the default UNUserNotificationCenter's delegate

that guide
{"aps":{"badge":2}}
{"aps":{"category":"CATEGORY_NAME"}}
{"aps":{"sound":"mysound.caf"}}
{"aps":{"content-available":1}}
// If you did not use BatchUNUserNotificationCenterDelegateregisterAsDelegate() but instanciated your own instance, use it instead of `sharedInstance`.
BatchUNUserNotificationCenterDelegate.sharedInstance.showForegroundNotifications = false
// If you did not use [BatchUNUserNotificationCenterDelegate registerAsDelegate] but instanciated your own instance, use it instead of `sharedInstance`.
[BatchUNUserNotificationCenterDelegate sharedInstance].showForegroundNotifications = false;
 [BatchPush setSupportsAppNotificationSettings:true]

Migration Guides

Deeplinking

URLs present in notifications and mobile landings/in-app messages are handled by the Batch SDK in the following ways:

  • the default implementation by the SDK will automatically call -[UIApplication openURL:] with any URL it encounters

  • alternatively you can use a BatchDeeplinkDelegate to handle the URL your way

let delegate: BatchDeeplinkDelegate
// deeplinkDelegate is a class property of the Batch class
BatchSDK.deeplinkDelegate = delegate

// Then make your delegate implement the only function from the BatchDeeplinkDelegate protocol
func openBatchDeeplink(_ deeplink: String)
id<BatchDeeplinkDelegate> delegate;
// deeplinkDelegate is a class property of the Batch class
BatchSDK.deeplinkDelegate = delegate;

// Then make your delegate implement the only method from the BatchDeeplinkDelegate protocol
- (void)openBatchDeeplink:(nonnull NSString*)deeplink;

The SDK references your delegate weakly so make sure you hold a strong reference to it. A good idea could be to use your application delegate as the deeplink delegate.

Universal links

Universal links let you connect to content deep inside your app using standard HTTP or HTTPS links (see documentation on the ).

As Universal links cannot be opened from within an app that supports them using UIApplication.openURL, some extra configuration is needed.

1.18.0 Once you have configured your app to handle universal links, you will need to declare your associated domains to the Batch SDK like in your Associated Domains Entitlement file.

For older SDK versions, you will have to use a BatchDeeplinkDelegate to handle these URLs manually.

If your site uses multiple subdomains (such as example.com, www.example.com, or support.example.com), each requires its own entry like in the Associated Domains Entitlement. Make sure to only include the desired subdomain and the top-level domain. Don’t include path and query components or a trailing slash (/).

Custom URL schemes

Custom URL schemes, like customScheme://first-page, provide a way to reference resources inside your app (see documentation on the ).

This type of URL is properly handled without a deeplink delegate. However, if you find yourself using a deeplink delegate, you will need to pass your custom-scheme URL manually to -[UIApplication openURL:]. This is a necessary step, considering setting a deeplink delegate overrides the default implementation of the SDK.

General

Batch sample apps

If you want to see a proper integration of the Batch SDK and to test any of its functionalities, you can take a look at our sample apps.

→

They are currently written for iOS in both Swift and Objective-C.

Apple Developper website
Apple Developer website
class AppDelegate: UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        BatchSDK.associatedDomains = ["example.com", "www.example.com"]
        // ...
    }
}
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [BatchSDK setAssociatedDomains:@[@"example.com", @"www.example.com"]];
    // ...
}
Manual framework integration
1

Step 1

First, download the SDK and Include Batch.xcframework in your project by dragging and dropping it into the Project Navigator as shown here:

Project Navigator view

Unless you manually copied the XCframework folder into your project's directory, check Copy items if needed. Make sure the only checked target is your Application target: extensions should be unchecked.

Xcode add framework to project
2

Step 2

Be sure Batch.xcframework and libsqlite3.tbd are in the Build Phases of your application target:

3

Step 3

In order to avoid a link issue when building your application, add -ObjC -lz to Other Linker Flags, under Build Settings of your application target.

You can now proceed with the

Generating the .p12 certificate

Creating a certificate

Batch servers need to have a certificate in order to communicate with Apple Push Notification Services (APNS). This certificate is generated for an unique Application Identifier (App ID).

1

Step 1.

Open your Keychain application on your Mac. In the menu bar, select "Keychain Access" → "Certificate Assistant" → "Request a Certificate From a Certificate Authority…"

Keychain

Then, enter your information in the required fields and select "Saved to disk". Save the "CertificateSigningRequest.certSigningRequest" file to your Desktop.

Certificate
2

Step 2.

Head to the , open the Identifiers section and create a new App ID (or choose an existing Explicit App ID) to configure Push Notifications.

Scroll down to the Push Notifications section, check it if needed and click configure.

Under "Production SSL Certificate", click on "Create Certificate...". The Production certificate can push both sandbox and production environments.

The next screen will ask us to upload the Certificate Signing Request (CSR), which we have created earlier.

Select "Choose File…" and locate the .certSigningRequest file on your desktop. Finally, select "Continue" and download the generated SSL certificate.

Generating the .p12 file

Now you have the certificate, you need to have it with its key bundled into a .p12 file format.

1

Step 1.

First, add the certificate to your Keychain by double clicking on the downloaded SSL certificate.

In Keychain Access, pick the default keychain (login on English systems) using the sidebar. Then, click on "My Certificates" and find the certificate you just added. It should be called "Apple Push Services:".

Certificates called "Apple Production IOS Push Services" and "Apple Development IOS Push Services" are not supported anymore. Please generate a new Production Certificate

2

Step 2.

Make sure that the certificate is collapsed (the arrow on the left ( > ) should point to the right). Right-click on the certificate, select "Export Apple Push Services:…", and save it as a .p12 file.

You will be prompted to enter a password which will be used to protect the exported certificate.

If the Personal Information Exchange (.p12) option is grayed out in the export sheet, make sure

3

Step 3.

Now go to Batch's dashboard → ⚙ Settings → Channel -> iOS, and upload your P12 certificate.

Troubleshooting: If you're having trouble uploading your certificate, you can check our .

Generating your Provisioning Profile

After creating a new App ID or modifying your existing one, you need to (re)generate your provisioning profile and build with it.

1

Step 1.

Go to Apple Developer website, then click on "Provisioning Profiles" from the iOS Apps section.

Click on the "+" button to create a new iOS Provisioning Profile or select an existing one.

If you already have one, make sure it has Push Notifications in the Enabled Services field.

2

Step 2.

In the Provisioning Profile creation wizard:

  • Select Type: Choose "App Store".

  • Configure: Choose the App ID you created before from the drop down and make sure to select your iOS Production certificate.

  • Generate: Choose a name for this provisioning profile, such as "Batch Push App Profile" and select "Generate"

3

Step 3.

Install the profile by double-clicking on the downloaded file.

Your new provisioning profile should appear in the Provisioning Profiles section in your Xcode settings under the "Accounts" section. Make sure that your developer certificate is installed in your Keychain.

Batch Push campaigns are sent to both the apps built with your Development and the Distribution provisioning profile.

Problems with your application delegate

If Batch tells you that it was unable to automatically integrate with your application delegate, it's probably because there is a conflict with another SDK.

Try calling [BatchSDK startWithAPIKey:] earlier in your application delegate.

Download the sample app on GitHub

Batch 1.16

Batch 1.16 comes with a number of changes regarding the distribution of the SDK and it's extension counterpart.

This migration guide will help you update your implementation.

iOS minimum version

Batch 1.16 requires iOS 10.0 or higher. If your app targets an older version of iOS, please downgrade to 1.15.

Migrating from the older binary BatchExtension

With the 1.16 release, BatchExtension has been rewritten and open-source in two repositories:

  • (Swift version, recommended)

  • (Objective-C version, should only be used if you cannot use the Swift one)

This marks the end of the binary distribution of BatchExtension, which used to be bundled with the SDK downloads. The only way to integrate BatchExtension in your application from now on is using the source. Fortunately, package managers make this easy to deal with!

Migrating to the new BatchExtension is done in three steps:

  • Remove the old BatchExtension framework

  • Add the new one using a package manager

  • Update your code to match the new API signatures, if needed.

Removing the old BatchExtension

Since BatchExtension has never been distributed on a package manager, you had to copy it in your project and add a script phase.

In order to remove the framework, simply find it in the Project Navigator (which is the first tab of the left sidebar), and delete it:

When asked what to do with the deletion, pick Move To Trash.

Xcode should have automatically removed the framework reference from the Link Binary With Libraries and Embed Frameworks build steps.

Now, go in your main target's Build Steps and find the Run Script phase that refences a script which path contains BatchExtension.framework/strip-frameworks.sh. Be careful, as you might have multiple script phases. Delete it.

BatchExtension has now been fully removed from your project. Now, we have to add it back!

Adding the new BatchExtension using a package manager

Note: If your extension code is written in Objective-C and uses BAENotificationServiceExtension, please use the Objective-C version of the extension.

The extension is available on Swift Package Manager, CocoaPods and Carthage.

Please which will walk you through integrating the extension, and come back here once done.

Updating your code

If the extension builds without issues, you do not have anything to do!

If it doesn't, what you have to do depends on how you integrated the extension and which language you use:

You're using Swift code

BatchExtension 3.0.0 introduces the following source breaking changes for Swift users:

  • Classes have been unprefixed (except BAENotificationServiceExtension). For example, if your Swift code references BAERichNotificationHelper, you need to change it to RichNotificationHelper.

  • RichNotificationHelper.appendRichData(to: completionHandler:) has been renamed. It is now RichNotificationHelper.appendRichData(toContent: completionHandler:). Xcode offers a quickfix for this.

You're using Objective-C code

While the public API doesn't change, you might run into the following message: Cannot subclass a class that was declared with the 'objc_subclassing_restricted' attribute. This happens if you used the standard version of the extension using Swift PM. To fix this, use the Objective-C version of the extension as described in the , or rewrite the extension code in Swift (do not forget to update the extension's Info.plist with the new class name).

If you run into any other issue, please contact our support team to get help on your migration.

Migrating to XCFramework

Batch 1.16 is now distributed as a static XCFramework instead of a legacy fat static framework. While the legacy framework format is still distributed, we encourage you to switch to the XCFramework build as soon as possible.

CocoaPods

Batch 1.16.0 and higher are distributed as a XCFramework in CocoaPods. CocoaPods should handle the transition without needing any action on your end.

If you're running into issues, make sure you've updated to the latest CocoaPods version for XCFrameworks support.

Carthage

As of writing, Carthage (ver 0.36.0) does not support XCFrameworks. Therefore, Batch is still distributed as a fat framework for Carthage.

iOS Simulator for Apple Silicon and Mac Catalyst support are not available with carthage.

Swift Package Manager

We've heard loud and clear that developers would like to use Swift Package Manager to add Batch in their app. Unfortunately due to bugs in Xcode 12's handling of binary frameworks in SPM packages, Batch isn't distributed there yet. We will reevaluate this decision when a new version of Xcode that fixes the issues is released.

.

Manual Integration

Replacing your manual Batch integration with the XCFramework distribution is quite easy.

First, delete Batch.embeddedframework (or Batch.framework) in the Project Navigator. Xcode should remove all of the Framework's reference in the build steps. Pick Move To Trash when asked.

Then, drag & drop Batch.xcframework in the Project Navigator where the framework you deleted was. Unless you manually copied the XCframework folder into your project's directory, check Copy items if needed. Make sure the only checked target is your Application target: extensions should be unchecked.

Xcode should have added Batch.xcframework in the Link Binary With Libraries build step. Add it manually if this did not happen.

Your project should now build!

"My Certificates"
is selected in Keychain Access.
Then download the generated provisioning profile from the next screen by selecting the "Download" button.
integration of the SDK
Apple Developer Member center
troubleshooting documentation
Link with libraries build phase
Linker flags
Entitlements
SSL certificate popup
CSR Upload
Generated Certificate
Keychain export menu
Keychain export password prompt
BatchSettings
https://github.com/BatchLabs/Batch-iOS-SDK-Extension
https://github.com/BatchLabs/Batch-iOS-SDK-Extension-ObjC
click here to open the rich push setup guide
rich push setup guide
Related Swift forum thread
Xcode Project Navigator extension deletion
Xcode script phase deletion
Xcode Project Navigator framework deletion
Xcode add framework to project
Xcode Link with libraries build step

Batch 2.0

Batch SDK v2 is a major release, which introduces breaking changes from 1.x. This guide describes how to update your application when using a previous version.

Upgrading the SDK version

⚠️ Batch SDK 2.0 now requires Xcode 15.3 and iOS 13.0 or higher.

To upgrade from v1 to v2, you need to change the SDK version according to your package manager:

Update by using the "update package" option from Xcode or updating your package.swift.

Package.swift
.package(url: "https://github.com/BatchLabs/Batch-iOS-SDK", from: "2.0.0")

Update your Podfile as following and run pod update.

Update your Cartfile if necessary and run carthage update Batch.

Linking the SDK

Batch is now distributed as a dynamic framework. This mean you can now safely link Batch between multiple framework targets!

Since Batch is now a , the additional metadata induced by this feature makes the XCFramework distribution heavier, but has no effect in the final size of Batch in your app once compiled.

Vision OS

This version introduced the visionOS support but some features are still unsupported like:

  • In-App messaging and mobile landings are unavailable.

  • In-App rating is not supported on visionOS due to an OS limitation.

Core migration

Renaming default module

This version introduced an important change since the default Batch module has been renamed into BatchSDK. This mean you have to replace all your calls to this module like when starting the SDK.

Advertising Identifier

The iOS Batch SDK 1.21 had removed automatic collection of IDFA (Identifier For Advertisers). This version has totally dropped the support of the IDFA and you can no longer set an advertising id to Batch since all related APIs have been removed.

Advanced Information

The Batch SDK v1 allowed you to disable advanced information generally with setUseAdvancedDeviceInformation(false). This has been removed and replaced with a more fine-tuning control of what you want to enable with:

All data configurable with this API are now disabled by default and should be enabled if you want to use it.

For more information, please visit our .

Deprecated APIs

All deprecated APIs in the SDK v1 have been removed. Some other APIs have been renamed/reworked to feel Swift native. To see in details the differences, please visit our .

Project migration

This version follows Batch's pivot to being an omnichannel platform. It allows you to collect data for your Projects and Profiles. If you are not familiar with these two concepts, please see beforehand.

Profile Attributes

First of all, most of the user-related write APIs have been removed. Reading methods are still usable since we do not provide yet a way to get synced data for a Profile, but keep in mind that the data returned is only about your installation and not your Profile.

To interacts with our user-centered model, you should now use the BatchProfile module. Let's see a migration example.

If you were previously doing something like that:

You should now do:

For more information, please see the following sections :

Profile Data migration

To make it easier to collect data to a Profile, Batch has added two automatic ways to migrate old installation's data on a Profile. So the first time a user will launch your application running on v2 :

  • He will be automatically identified (logged-in) if he had a Batch custom_user_id set on the local storage.

  • Its natives (language/region) and customs data will be automatically migrate to a Profile if your app is attached to a Project.

These migrations are enabled by default, but you may want to disable them, to do so add the following before starting the SDK:

For more information, please visit our

Event data

Android Batch SDK v2 introduced two new types of attribute that can be attached to an event : Array and Object.

What's change:

  • BatchEventData has be renamed into BatchEventAttributes.

  • addTag API is no longer available and has been replaced by putStringArray with the $tags reserved key.

  • Optional withLabel

Note: Limits are unchanged and still 200 chars max for $label and 10 items max for $tags .

So if you were previously doing something like:

You should now do:

For more information, please visit our .

To see in details what's precisely changed since v1 please consult our or visit the .

Manual integration

In order to make your SDK integration as easy as possible, Batch Push automatically integrates into your application delegate by using a technique called "method swizzling".

Even though we've taken the greatest care when writing our swizzling code, you may encounter some cases where you don't want that, such as:

  • Incompatibility with other SDKs that also try to integrate themselves

  • Incompatibility with third party app development solutions

  • Swizzling breaks your delegate's architecture

  • You don't want code swizzled on your behalf

That's why, starting with Batch 1.5.3, we support a fully manual integration of the SDK.

We advise that you only resort to this if swizzling is problematic. On most apps this is not a problem, but if you manually integrate, you will have to check the changelogs on each update to see if Batch requires new methods for the manual integration.

To disable automatic integration, simply call:

This must be done before [BatchSDK startWithAPIKey:].

Then, you'll have to put calls to BatchPush where needed, in order to ensure that all Batch Push functionality works. Not implementing any of them will cause issues.

Here's a sample app delegate:

Legacy documentation

If using Batch 1.15 or lower on an application that supports iOS 8 and 9, you need to implement some extra methods:

On iOS 10 and higher, you should also implement UNUserNotificationCenterDelegate. How to correctly integrate it with Batch does not change in manual mode, and is detailed in .

Adding notification actions

iOS 8 introduced actionnable notifications: action buttons can be attached to notifications. Batch includes support for them: this documentation describes how to make the best of Batch's features and how to integrate them with iOS' native features, including how to work with the new iOS 10 APIs without breaking backwards compatibility.

This guide has no intention of being a full replacement of .

Registering with the framework

In order to be able to use action buttons in your notifications, you will have to register them to the framework in the application itself.

To do so, you'll create Actions

parameter is no longer available and has been replaced by a
$label
reserved key under
BatchEventAttributes
.
mergeable dynamic library
automatic data collection guide
changelog
this guide
Custom user id
Custom region/language
Custom user attributes
Email subscription
profile data migration guide
custom event guide
changelog
APIs Reference
Podfile
pod 'Batch', '~> 2.0'
Cartfile
github "BatchLabs/Batch-iOS-SDK" ~> 2.0
Intercepting Notifications
BatchPush.disableAutomaticIntegration()
[BatchPush disableAutomaticIntegration];
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
    BatchPush.disableAutomaticIntegration()
    BatchSDK.start(withAPIKey: "YOUR API KEY")

    // You MUST have a UNUserNotificationCenterDelegate implementation.
    // Batch provides BatchUNUserNotificationCenterDelegate as a default one: if you have your own, integrate Batch into it.
    // See "Intercepting notifications" for more info.
    // Available from Batch 1.16
    BatchUNUserNotificationCenterDelegate.registerAsDelegate()

    // Ask for the permission to display notifications
    // The push token will automatically be fetched by the SDK
    BatchPush.requestNotificationAuthorization()
    
    // Alternatively, you can call requestNotificationAuthorization later
    // But, you should always refresh your token on each application start
    // This will make sure that even if your user's token changes, you still get notifications
    // BatchPush.refreshToken();
    
    return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    BatchPush.handleDeviceToken(deviceToken)
}
and group sets of them into
Categories
.
Categories
are what iOS will use to display a set of predefined buttons in local or remote notifications. It is important that you figure out what
categories
you will want to associate to your notifications as updating them will require you to submit an updated build to the App Store and have your users update your app before you can push with your new actions.

Actions are defined by:

  • An identifier. Your app will be called back with this identifier once the user tapped on the action so you can perform the action.

  • A label. It is the text that the user will see.

  • Optional behavior options such as:

    • Destructive (the button will appear red)

    • Touch ID/Passcode requirement

    • Foreground/Background action (whether tapping the button will open your app or perform the action in the background)

    • ...

You can delcare these actions using the standard Apple UIKit documentation iOS8/9 / iOS10.

Once you've declared your actions and put them into categories, you can register them to Batch:

You can also use the native UIKit APIs to register them, as per Apple's documentation.

You'll find an example implementation in Swift here in our Batchcasts sample project. It handles iOS 8/9/10 API changes and registers multiple actions in a category.

Responding to actions

iOS 10 (UserNotifications)

If you're using the UserNotifications framework, iOS will call you back in your UNUserNotificationCenterDelegate instance you've set as the UNUserNotificationCenter delegate.

For that, you'll need to implement userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

You'll also need to make sure you don't accidentally try to run your own code for actions that are not linked to action buttons:

Earlier iOS versions

As most legacy notifications callbacks, iOS will call you back on your app delegate.

You'll need to implement application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:.

Link with Custom Actions

A great way to clean up your code is to use Custom Actions and link them to your action buttons. It allows you to share code between the legacy and new callbacks, but also make your actions available to Mobile Landings and any future feature that will support calling actions you've defined remotely.

Once you've created your Custom Actions, make sure you register your UNNotificationAction instances with the same identifier as the ones you've picked for your Batch Custom Actions.

Then, you can trigger them manually from the callbacks:

While Apple also calls action buttons added to notifications "Custom Actions", they are not directly linked to Batch's Custom Actions

Handling both notification APIs at the same time

Starting with iOS 10, Apple has deprecated UIUserNotificationAction and related classes in favor of UNNotificationAction. Applications compatible with iOS version lower than iOS 10 will have to deal with compatibility.

There are three ways of dealing with this:

Using the right API depending on the OS version your app is running on.

The way is the recommended one. You can use Objective-C/Swift feature detection and register accordingly:

Note: A full example of this implementation can be found in our BatchCasts iOS Sample

Using UIUserNotificationAction regardless of the OS version using Batch.

If you're using Batch to register for notifications rather than UIKit methods, switching to the new UserNotifications framework has already been done for you.

Calling BatchPush.registerForRemoteNotifications(<Categories set>) with legacy (UI*UserNotification) actions and categories will cause Batch to convert them to UNNotificationAction and UNNotificationCategory instances and use the new APIs transparently.

If you define categories with both Default and Minimal contexts for your action sets, Batch will complain in the logs, as this doesn't exist anymore with the new APIs. A long term fix is to migrate to the right API according to the iOS version you're running on, as described earlier.

Using UIUserNotificationAction regardless of the OS version using UIKit methods.

Registering notifications this way is supported by iOS, but should not be used: You should use the UserNotifications framework on iOS 10, since the old APIs have been deprecated. You'll also have better callback support for anything related to notifications in your app.

Adding actions to your notification

You can now add actions to your iOS notifications by adding a category key to the payload of your push. Here is how it looks:

Apple's official documentations of notification actions
Push example with actions and image
BatchSDK.start(withAPIKey: "YOUR_API_KEY")
[BatchSDK startWithAPIKey:@"MY_API_KEY"];
BatchSDK.updateAutomaticDataCollection { config in
    config.setGeoIPEnabled(true) // Enable GeoIP resolution on server side
    config.setDeviceModelEnabled(true) // Enable automatic collection of the device model information
}
[BatchSDK updateAutomaticDataCollection:^(BatchDataCollectionConfig * _Nonnull config) {
  [config setGeoIPEnabled:true];  // Enable GeoIP resolution on server side
  [config setDeviceModelEnabled:true]; // Enable automatic collection of the device model information
}];
let editor = BatchUser.editor()
editor.setIdentifier("john.doe")
editor.setLanguage("en")
editor.setRegion("US")
try? editor.setEmail("john.doe@batch.com")
editor.setEmailMarketingSubscriptionState(BatchEmailSubscriptionState.subscribed)
editor.setAttribute(26, forKey:"age")
editor.removeAttribute(key:"firstname")
editor.addTag("has_bought", inCollection: "actions") 
editor.removeTag("has_bought", fromCollection: "actions") 
editor.clearTagCollection("actions")
editor.save()

BatchUserDataEditor *editor = [BatchUser editor];
[editor setIdentifier:@"john.doe"];
[editor setLanguage:@"en"]; 
[editor setRegion:@"US"]; 
[editor setEmail:@"john.doe@batch.com" error:nil]; 
[editor setEmailMarketingSubscriptionState:BatchEmailSubscriptionStateSubscribed]; 
[editor setIntegerAttribute:@26 forKey:@"age" error:nil];
[editor removeAttributeForKey:@"firstname"];
[editor addTag:@"has_bought" inCollection:@"actions"];
[editor removeTag:@"has_bought" fromCollection:@"actions"];
[editor clearTagCollection:@"actions"]; 
[editor save];
BatchProfile.identify("john.doe")
BatchProfile.editor { editor in
    try? editor.setLanguage("en")
    try? editor.setRegion("US")
    try? editor.setEmailAddress("john.doe@batch.com")
    editor.setEmailMarketingSubscriptionState(BatchEmailSubscriptionState.subscribed)
    try? editor.set(attribute: 26, forKey: "age")
    try? editor.removeAttribute(key:"firstname")
    try? editor.addToStringArray(item: "has_bought", forKey: "actions")
    try? editor.removeFromStringArray(item: "has_bought", forKey: "actions")
    try? editor.removeAttribute(key:"actions")
    // Don't forget to use `editor.save()` if you use the editor in a local variable and not a closure like here.
}
[BatchProfile identify:@"john.doe"];
[BatchProfile editWithBlock:^(BatchProfileEditor * _Nonnull editor) {
    [editor setLanguage:@"en" error:nil];
    [editor setRegion:@"US" error:nil];
    [editor setEmailAddress:@"john.doe@batch.com" error:nil];
    [editor setEmailMarketingSubscriptionState:BatchEmailSubscriptionStateSubscribed];
    [editor setIntegerAttribute:26 forKey:@"age" error:nil];
    [editor removeAttributeForKey:@"firstname" error:nil];
    [editor addItemToStringArrayAttribute:@"has_bought" forKey:@"has_bought" error:nil];
    [editor removeItemFromStringArrayAttribute:@"has_bought" forKey:@"has_bought" error:nil];
    [editor removeAttributeForKey:@"actions" error:nil];
     // Don't forget to use `[editor save]' if you use the editor in a local variable and not a closure like here.
}];
import Batch

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Disable profile's migration
    BatchSDK.setDisabledMigrations([
     
       // Whether Batch should automatically identify logged-in user when running the SDK for the first time.
       // This mean user with a custom_user_id will be automatically attached a to a Profile and could be targeted within a Project scope.
       .customID,
       
       // Whether Batch should automatically attach current installation's data (language/region/customDataAttributes...)
       // to the User's Profile when running the SDK for the first time.
       .customData
     ])
    // Then start Batch SDK.
    BatchSDK.start(withAPIKey: "YOUR_API_KEY")
}
@import Batch;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      // Disable profile's migration
      [BatchSDK setDisabledMigrations: 
        // Whether Batch should automatically identify logged-in user when running the SDK for the first time.
        // This mean user with a custom_user_id will be automatically attached a to a Profile and could be targeted within a Project scope.
        BatchMigrationCustomID |
        // Whether Batch should automatically attach current installation's data (language/region/customDataAttributes...)
        // to the User's Profile when running the SDK for the first time.
        BatchMigrationCustomData
      ];
  
  // Then start Batch SDK.
  [BatchSDK startWithAPIKey:@"MY_API_KEY"];

  return YES;
}
let data = BatchEventData()
data.add(tag: "squash")
data.add(tag: "daily_digest")
data.put(true, forKey: "premium")
data.put("123456", forKey: "id")
BatchUser.trackEvent("read_article", withLabel: "sports", data: data)
BatchEventData *data = [BatchEventData new];
[data addTag:@"squash"];
[data addTag:@"daily_digest"];
[data putBool:@(YES) forKey:@"premium"];
[data putString:@"123456" forKey:@"id"];
[BatchUser trackEvent:@"read_article" withLabel:@"sports" associatedData:data];
let attributes = BatchEventAttributes { data in
    data.put("sports", forKey: "$label")
    data.put(["squash", "daily_digest"], forKey: "$tags")
    data.put("123456", forKey: "id")
    data.put(true, forKey: "premium")
}
BatchProfile.trackEvent(name: "read_article", attributes: attributes)
BatchEventAttributes *attributes = [BatchEventAttributes new];
[attributes putString:@"sports" forKey:@"$label"];
[attributes putStringArray:@[@"squash", @"daily_digest"] forKey:@"$tags"];
[attributes putString:@"123456" forKey:@"id"];
[attributes putBool:YES forKey:@"premium"];
[BatchProfile trackEventWithName:@"read_article" attributes:attributes];
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [BatchPush disableAutomaticIntegration];
    [BatchSDK startWithAPIKey:@"KEY"];

    // You MUST have a UNUserNotificationCenterDelegate implementation.
    // Batch provides BatchUNUserNotificationCenterDelegate as a default one: if you have your own, integrate Batch into it.
    // See "Intercepting notifications" for more info.
    // Available from Batch 1.16
    [BatchUNUserNotificationCenterDelegate registerAsDelegate];

    // Ask for the permission to display notifications
    // The push token will automatically be fetched by the SDK
    [BatchPush requestNotificationAuthorization];

    // Alternatively, you can call requestNotificationAuthorization later
    // But, you should always refresh your token on each application start
    // This will make sure that even if your user's token changes, you still get notifications
    // [BatchPush refreshToken];
    
    return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    [BatchPush handleDeviceToken:deviceToken];
}
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {

    BatchPush.handleRegister(notificationSettings)
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
    BatchPush.handleNotification(userInfo)
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    BatchPush.handleNotification(userInfo)
    completionHandler(.newData) // Adjust the result accordingly
}

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
    [BatchPush handleRegisterUserNotificationSettings:notificationSettings];
}

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

// Alternatively, implement this one:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    [BatchPush handleNotification:userInfo];
    completionHandler(UIBackgroundFetchResultNewData);
}
BatchPush.setNotificationsCategories(<your categories Set>)
BatchPush.registerForRemoteNotifications()
[BatchPush setNotificationsCategories:<your categories NSSet>];
[BatchPush registerForRemoteNotifications];
import UserNotifications
import Batch

@available(iOS 10.0, *)
@objc
class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate {

    func userNotificationCenter(center: UNUserNotificationCenter, didReceiveNotificationResponse response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
        
        if response.actionIdentifier != UNNotificationDefaultActionIdentifier &&
            response.actionIdentifier != UNNotificationDismissActionIdentifier {
            // Perform your action here
        }
        
        BatchPush.handle(userNotificationCenter: center, didReceive: response)
        completionHandler()
    }
}
@import UserNotifications;
@import Batch;

@interface NotificationCenterDelegateObjc : NSObject <UNUserNotificationCenterDelegate>
@end

@implementation NotificationCenterDelegateObjc

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)())completionHandler
{
    if (![UNNotificationDefaultActionIdentifier isEqualToString:response.actionIdentifier] &&
        ![UNNotificationDismissActionIdentifier isEqualToString:response.actionIdentifier]) {
        // Handle your action here
    }
    
    [BatchPush handleUserNotificationCenter:center didReceiveNotificationResponse:response];
    completionHandler();
}

@end
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
    // Handle the action here
    completionHandler()
}
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler
{
    // Handle your action here
    completionHandler();
}
// iOS 8/9
// In your application delegate

func application(_ application: UIApplication,
                 handleActionWithIdentifier identifier: String?,
                 forRemoteNotification userInfo: [AnyHashable : Any],
                 completionHandler: @escaping () -> Void) {
    if let identifier = identifier {
        var args: [String : AnyObject] = [:]
        if let payloadArgs = userInfo["action_args"] as? [String : AnyObject] {
            args = payloadArgs
        }
        BatchActions.perform(actionIdentifier: identifier, arguments: args)
    }
    
    completionHandler()
}

// iOS 10
import UserNotifications
import Batch

@available(iOS 10.0, *)
@objc
class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate {

    func userNotificationCenter(center: UNUserNotificationCenter, didReceiveNotificationResponse response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
        
        if response.actionIdentifier != UNNotificationDefaultActionIdentifier &&
            response.actionIdentifier != UNNotificationDismissActionIdentifier {
            // If you want to add arguments to your action, this sample code will read from the "action_args" key of your custom payload
            var args: [String : AnyObject] = [:]
            if let payloadArgs = response.notification.request.content.userInfo["action_args"] as? [String : AnyObject] {
                args = payloadArgs
            }
            BatchActions.perform(actionIdentifier: response.actionIdentifier, arguments: args)
        }
        
        BatchPush.handle(userNotificationCenter: center, didReceive: response)
        completionHandler()
    }
}
// iOS 8/9
// In your application delegate

- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forRemoteNotification:(NSDictionary *)userInfo
  completionHandler:(void (^)())completionHandler
{
    if (identifier) {
        // If you want to add arguments to your action, this sample code will read from the "action_args" key of your custom payload
        NSDictionary *args = @{};
        NSObject *payloadArgs = userInfo[@"action_args"];
        if ([payloadArgs isKindOfClass:[NSDictionary class]]) {
            args = (NSDictionary*)payloadArgs;
        }
        
        [BatchActions performActionIdentifiedBy:identifier withArguments:args];
    }
    
    completionHandler();
}

// iOS 10
@import UserNotifications;
@import Batch;

@interface NotificationCenterDelegateObjc : NSObject <UNUserNotificationCenterDelegate>
@end

@implementation NotificationCenterDelegateObjc

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)())completionHandler
{
    if (![UNNotificationDefaultActionIdentifier isEqualToString:response.actionIdentifier] &&
        ![UNNotificationDismissActionIdentifier isEqualToString:response.actionIdentifier]) {
        // If you want to add arguments to your action, this sample code will read from the "action_args" key of your custom payload
        NSDictionary *args = @{};
        NSObject *payloadArgs = response.notification.request.content.userInfo[@"action_args"];
        if ([payloadArgs isKindOfClass:[NSDictionary class]]) {
            args = (NSDictionary*)payloadArgs;
        }
        
        [BatchActions performActionIdentifiedBy:response.actionIdentifier withArguments:args];
    }
    
    [BatchPush handleUserNotificationCenter:center didReceiveNotificationResponse:response];
    completionHandler();
}

@end
if #available(iOS 10, *) {
    // We're on iOS 10
    registerActions()
} else {
    // We're on iOS 8/9
    registerLegacyActions()
}
if ([UNNotificationAction class]) {
    // We're on iOS 10
    [self registerActions];
} else {
    // We're on iOS 8/9
    [self registerLegacyActions];
}
{"aps":{"category":"CATEGORY_NAME"}}

Custom actions

Batch Actions is a module allowing you to register remotely-configurable runnable code to the SDK, when simple deeplinks wouldn't be enough. They can be triggered at any time by the SDK, allowing you to focus on the action code rather than when to trigger it.

Batch comes with builtin actions (deeplinking, user data edition, etc...)

Note: On iOS, actions are mainly used for mobile landings, but their usage will be extended in future Batch versions.

Registering an action

An action has two components:

  • An identifier (case-unsensitive string), which will allow you to reference this action

    • While the identifier string is up to you, it cannot start with "batch.": these identifiers are reserved for built-in actions.

  • An implementation block

Registering them is easy, and should be done every time your app starts, right before starting Batch:

BatchActions.register(BatchUserAction(identifier: "<YOUR_ACTION_NAME>",
                                      actionBlock: { (identifier: String, arguments: [String : Any], source: BatchUserActionSource?) in
    // Your action code here
BatchUserActionBlock alertActionBlock = ^(NSString * _Nonnull identifier, NSDictionary<NSString *,NSObject *> * _Nonnull arguments, id<BatchUserActionSource>  _Nullable source) {
    // Your action code here
};
[BatchActions registerAction

The source argument represents what caused the action to be triggered. You can use isKindOfClass (or a swift cast using as?) to check what source it comes from, and read what you need. Two sources classes are currently used: BatchPushMessage and BatchManualUserActionSource. More will be added in the future.

Unregistering an action

Simply call the unregister method with the previously set identifier:

Triggering an action

You might want to re-use the code you've already registered to Batch from your own code by triggering an action.

Simply give Batch the action identifier and arguments, and it will call your action:

Note: Built-in Batch actions are reserved to the SDK and not triggerable that way

Built-in actions

Batch provide a set of pre-defined actions, allowing you to easily trigger a specific behavior without implementing them yourself.

batch.dismiss

Simply dismiss the notification and exit

Arguments

N/A


batch.deeplink

Open the provided deeplink (will call the Deeplink Interceptor if one is set).

Arguments (required)

Name
Value

l

String - Required The deeplink to open E.g.{"l":"https://google.com"}

li

Boolean - Optional - Default false If true the Batch SDK will try to open your deeplink inside the app E.g.{"li":false}


batch.ios_request_notifications

Show the notification permissions pop-up.

Arguments

N/A


batch.ios_redirect_settings

Open the notifications settings of the current application.

Arguments

N/A


batch.ios_smart_reoptin

Checks if the user has already been asked for notifications, if not it shows the notification permissions pop-up, otherwise it opens the notifications settings of the current application.

Arguments

N/A


batch.user.tag

Add or remove a tag associated with the current user.

Arguments (required)

Name
Value

a

String - Required The action to perform. Acceptable values are add and remove E.g.{"a":"remove"}

c

String - Required The collection to use when updating the tag E.g.{"c":"actions"}

t

String - Required The tag to update E.g.{"t":"has_bought"}


batch.user.event

Send an event with associated tags and data.

Arguments (required)

Name
Value

e

String - Required The name of the event to send E.g.{"e":"my_awesome_event"}

l

String - Optional - default null The label associated with the event E.g.{"l":"label1"}

t

Array - Optional - default empty list A list of tags to be send with the event E.g.{"t":["tag1","tag2","tag3"]}

a

Object - Optional - default empty object The data to send with the event E.g.{"a":{"key":"value","key2":12,"key3":true}}


batch.group

Execute a list of up to 10 nested actions successively.

Arguments (required)

Name
Value

actions

Array - Required A list of actions to perform. This list must not be longer than 10 elements and can't contains batch.group actions E.g.{"actions":[["batch.deeplink",{"l":"https://google.com"}],["batch.user.tag",{"add":"..."}]]}


batch.ios_tracking_consent

On iOS 14, asks for Tracking consent using the AppTrackingTransparency framework. If the user has already been asked and refused to consent, opens the app's settings where tracking can be enabled. Does nothing on iOS 13 and earlier, or if tracking has been restricted by the device's manager. Your application needs to have a 'NSUserTrackingUsageDescription' key in its Info.plist or else iOS will crash it. Available since Batch 1.16.

Arguments

N/A


batch.clipboard

Copy a text to the device's clipboard.

Arguments (required)

Name
Value

t

String - Required The text to copy E.g.{"t":"My awesome text !"}


batch.rating

On iOS 10.3+, asks for the user to rate the app using SKStoreReviewController (this API's restrictions apply). Available since Batch 1.17.

Arguments

N/A


Intercepting notifications

It is possible to get the raw payload of a notification (which includes your 'custom payload'), would you wish to do something with it. This is done using standard iOS APIs:

iOS 10

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

  • Unify local and remote notifications callbacks

BatchActions.unregister(actionIdentifier: "<YOUR_ACTION_NAME>")
[BatchActions unregisterActionIdentifier:@"<YOUR_ACTION_NAME>"];
BatchActions.perform(actionIdentifier: "alert", arguments: ["title": "Hello", "body": "How are you doing?"])
[BatchActions performActionIdentifiedBy:@"alert" withArguments:@{@"title":@"Hello", @"body":@"How are you doing?"}];
}))
:
[BatchUserAction userActionWithIdentifier
:
@"<YOUR_ACTION_NAME>"
actionBlock:alertActionBlock]];

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.

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

    @objc
    class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
        func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    
    // NotificationDelegate.h
    
    @import Foundation;
    @import UserNotifications;
    
    @interface NotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
    
    @end
    
    // NotificationDelegate.m
    #import "NotificationDelegate.h"
    
    @import Batch;
    
    

    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:

    Handling a custom payload

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

    iOS 9 and lower

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

    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 backward-compatibility, you'll only be called on one of them.

    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:

    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:

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

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

    class AppDelegate: UIResponder, UIApplicationDelegate {
        let notificationDelegate = NotificationDelegate()
        
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            if #available(iOS 10, *) {
                UNUserNotificationCenter.current().delegate = notificationDelegate
            }
        }
    }
    @interface AppDelegate ()
    {
        NotificationDelegate *notificationDelegate;
    }
    @end
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        if ([UNUserNotificationCenter class]) {
            notificationDelegate = [NotificationDelegate new];
            [[UNUserNotificationCenter currentNotificationCenter] setDelegate:notificationDelegate];
        }
    }
    
    @end
    // 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
      }
    }
    // 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
        }
      }
    }
           func application(application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable : Any],
           fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any])
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
    // 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
      }
    }
    // 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
        }
      }
    }
    BatchPush.enableAutomaticDeeplinkHandling(false)
    [BatchPush enableAutomaticDeeplinkHandling:NO];
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
        let deeplink = BatchPush.deeplink(fromUserInfo: userInfo)
    }
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
        NSString *deeplink = [BatchPush deeplinkFromUserInfo:userInfo];
    }
    [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([])
    }
    }
    @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

    Batch 3.0

    Batch SDK v3 is a major release, which introduces breaking changes from 2.x. This guide describes how to update your application when using a previous version.

    If your application is still using Batch SDK v1 please follow our migration guide from V1 beforehand.

    Upgrading the SDK version


    ⚠️ Batch SDK 3.0 now requires Xcode 16.3 and iOS 15.0 or higher.

    To upgrade from v2 to v3, you need to change the SDK version according to your package manager:

    Update by using the "update package" option from Xcode or updating your package.swift.

    Update your Podfile as following and run pod update.

    Update your Cartfile if necessary and run carthage update Batch.

    Messaging migration


    Batch SDK v3 adds compatibility for Mobile Landings with Push v2 which can be created from the Batch Dashboard's drag & drop composer.

    In-App Message Interception

    The method batchInAppMessageReady has been removed from the BatchMessagingDelegate protocol. Please replace its usage with the new BatchInAppDelegate protocol.

    Use the property access BatchMessaging.inAppDelegate to set your custom delegate.

    So if you were doing:

    You should now do:

    For more information, please see the In-App messaging section.

    Messaging Delegate

    The following methods in BatchMessagingDelegate have been removed:

    • batchMessageWasCancelledByAutoclose

    • batchMessageWasCancelledByUserAction

    • batchMessageWasCancelledByError

    Please update your implementation to use the batchMessageDidDisappear:(NSString *_Nullable)messageIdentifier reason:(BatchMessagingCloseReason)reason; method. The new BatchMessagingCloseReason enum will provide the context about why the message was closed.

    WebView actions will now trigger the updated batchMessageDidTriggerAction method, with the analyticsID of the action being passed as the ctaIdentifier.

    The index (NSInteger) parameter in batchMessageDidTriggerAction has been replaced by ctaIdentifier (NSString). The method signature is now

    For older MEP (Mobile Engagement Platform) messages, ctaIdentifier will be a string in the format "mepCtaIndex:" + index.

    The constant BatchMessageGlobalActionIndex now returns a NSString instead of an NSInteger. Update any comparisons or usage accordingly.

    So if you were doing:

    You should now do:

    For more information, please see the section.


    To see in details what's precisely changed since V2 please consult our or visit the .

    batchWebViewMessageDidTriggerAction
    manual mode
    listening lifecycle event
    changelog
    APIs Reference
    Package.swift
    .package(url: "https://github.com/BatchLabs/Batch-iOS-SDK", from: "3.0.1")
    Podfile
    pod 'Batch', '~> 3.0'
    Cartfile
    github "BatchLabs/Batch-iOS-SDK" ~> 3.0
    // Application delegate
    
    var inAppMsgDelegate = InAppMsgDelegate()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      BatchMessaging.delegate = inAppMsgDelegate
    }
    
    // InAppMsgDelegate implementation
    
    @objc
    class InAppMsgDelegate: NSObject, BatchMessagingDelegate {
        func batchInAppMessageReady(message: BatchInAppMessage) {
            // Your implementation
        }
    }
    
    // Application delegate
    
    @property InAppMsgDelegate *inAppMsgDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
      self.inAppMsgDelegate = [InAppMsgDelegate new];
      BatchMessaging.delegate = self.inAppMsgDelegate;
    }
    
    // InAppMsgDelegate implementation
    
    @interface InAppMsgDelegate : NSObject <BatchMessagingDelegate>
    @end
    
    @implementation InAppMsgDelegate
    
    - (void)batchInAppMessageReady:(nonnull BatchInAppMessage*)message
    {
     // Your implementation
    }
    
    @end
    
    // Application delegate
    
    var inAppMsgDelegate = InAppMsgDelegate()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      BatchMessaging.inAppDelegate = inAppMsgDelegate
    }
    
    // InAppMsgDelegate implementation
    
    @objc
    class InAppMsgDelegate: NSObject, BatchInAppDelegate {
        func batchInAppMessageReady(message: BatchInAppMessage) {
            // Your implementation
        }
    }
    
    // Application delegate
    
    @property InAppMsgDelegate *inAppMsgDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
      self.inAppMsgDelegate = [InAppMsgDelegate new];
      BatchMessaging.inAppDelegate = self.inAppMsgDelegate;
    }
    
    // InAppMsgDelegate implementation
    
    @interface InAppMsgDelegate : NSObject <BatchInAppDelegate>
    @end
    
    @implementation InAppMsgDelegate
    
    - (void)batchInAppMessageReady:(nonnull BatchInAppMessage*)message
    {
     // Your implementation
    }
    
    @end
    
    batchMessageDidTriggerAction:(BatchMessageAction *_Nonnull)action
                       messageIdentifier:(NSString *_Nullable)identifier
                           ctaIdentifier:(NSString *_Nonnull)ctaIdentifier;
    // Application delegate
    
    var messagingDelegate = MsgDelegate()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      BatchMessaging.delegate = inAppMsgDelegate
    }
    
    // BatchMessagingDelegate implementation
    
    class SampleBatchMessagingDelegate: NSObject, BatchMessagingDelegate {
        
        public func batchMessageDidAppear(messageIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchMessageDidAppear: \(messageIdentifier)")
        }
        
        public func batchMessageDidTriggerAction(_ action: BatchMessageAction, messageIdentifier identifier: String?, actionIndex index: Int) {
            print("SampleBatchMessagingDelegate - batchMessageDidTriggerAction: \(messageIdentifier)")
        }
       
        public func batchWebViewMessageDidTriggerAction(_ action: BatchMessageAction?, messageIdentifier: String?, analyticsIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchWebViewMessageDidTriggerAction \(String(describing:action)), tracking ID: \(messageIdentifier ?? "<nil>"), analyticsIdentifier:  \(analyticsIdentifier ?? "<nil>")")
        }
    
        public func batchMessageWasCancelledByAutoclose(_ messageIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchMessageWasCancelledByAutoclose: \(messageIdentifier)")
        }
    
        public func batchMessageWasCancelledByUserAction(_ messageIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchMessageWasCancelledByUserAction: \(messageIdentifier)")
        }
        
        public func batchMessageWasCancelledByError(_ messageIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchMessageWasCancelledByError: \(messageIdentifier)")
        }
        
        public func batchMessageDidDisappear(messageIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchMessageDidDisappear: \(messageIdentifier)")
        }
    }
    // AppDelegate.m
    @property SampleBatchMessagingDelegate *messagingDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
      self.messagingDelegate = [SampleBatchMessagingDelegate new];
      BatchMessaging.delegate = self.messagingDelegate;
    }
    
    // Header file (.h)
    @interface SampleBatchMessagingDelegate : NSObject <BatchMessagingDelegate>
    
    @end
    
    // Implementation file (.m)
    
    @implementation SampleBatchMessagingDelegate
    
    - (void)batchMessageDidAppear:(NSString* _Nullable)messageIdentifier
    {
        NSLog(@"SampleBatchMessagingDelegate - batchMessageDidAppear: %@", messageIdentifier);
    }
    
    - (void)batchMessageDidTriggerAction:(BatchMessageAction * _Nonnull)action messageIdentifier:(NSString *)identifier actionIndex:(NSInteger)index
    {
        NSLog(@"SampleBatchMessagingDelegate - batchMessageDidTriggerAction: %@", messageIdentifier);
    }
    
    - (void)batchWebViewMessageDidTriggerAction:(BatchMessageAction * _Nonnull)action messageIdentifier:(NSString *)identifier analyticsIdentifier:(NSString *)analyticsIdentifierex
    {
        NSLog(@"SampleBatchMessagingDelegate - batchWebViewMessageDidTriggerAction: %@", messageIdentifier);
    }
    
    - (void)batchMessageWasCancelledByAutoclose:(NSString * _Nullable)messageIdentifier
    {
        NSLog(@"SampleBatchMessagingDelegate - batchWebVbatchMessageWasCancelledByAutocloseiewMessageDidTriggerAction: %@", messageIdentifier);
    }
    
    - (void)batchMessageWasCancelledByUserAction:(NSString * _Nullable)messageIdentifier
    {
        NSLog(@"SampleBatchMessagingDelegate - batchMessageWasCancelledByUserAction: %@", messageIdentifier);
    }
    
    - (void)batchMessageDidDisappear:(NSString* _Nullable)messageIdentifier
    {
        NSLog(@"SampleBatchMessagingDelegate - batchMessageDidDisappear: %@", messageIdentifier);
    }
    
    @end
    // Application delegate
    
    var messagingDelegate = MsgDelegate()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      BatchMessaging.delegate = messagingDelegate
    }
    
    // BatchMessagingDelegate implementation
    
    class SampleBatchMessagingDelegate: NSObject, BatchMessagingDelegate {
        
        public func batchMessageDidAppear(messageIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchMessageDidAppear: \(messageIdentifier)")
        }
        
        public func batchMessageDidTriggerAction(_ action: BatchMessageAction, messageIdentifier identifier: String?, ctaIdentifier: String) {
            print("SampleBatchMessagingDelegate - batchMessageDidTriggerAction: \(messageIdentifier)")
            // For MEP messages, ctaIdentifier might be "mepCtaIndex:0", "mepCtaIndex:1", etc.
        }
        
        public func batchMessageDidDisappear(_ messageIdentifier: String?, reason: BatchMessagingCloseReason) {
            switch(reason) {
            case .action:
                print("SampleBatchMessagingDelegate - batchMessageDidDisappear: \(messageIdentifier)")
            case .auto:
                print("SampleBatchMessagingDelegate - batchMessageWasCancelledByAutoclose: \(messageIdentifier)")
            case .user:
                print("SampleBatchMessagingDelegate - batchMessageWasCancelledByUserAction: \(messageIdentifier)")
            case .error:
                print("SampleBatchMessagingDelegate - batchMessageWasCancelledByError: \(messageIdentifier)")
            }
        }
    }
    // AppDelegate.m
    @property SampleBatchMessagingDelegate *messagingDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
      self.messagingDelegate = [SampleBatchMessagingDelegate new];
      BatchMessaging.delegate = self.messagingDelegate;
    }
    
    // Header file (.h)
    @interface SampleBatchMessagingDelegate : NSObject <BatchMessagingDelegate>
    
    @end
    
    // Implementation file (.m)
    @implementation SampleBatchMessagingDelegate
    
    - (void)batchMessageDidAppear:(NSString* _Nullable)messageIdentifier
    {
        NSLog(@"SampleBatchMessagingDelegate - batchMessageDidAppear: %@", messageIdentifier);
    }
    
    - (void)batchMessageDidTriggerAction:(BatchMessageAction * _Nonnull)action messageIdentifier:(NSString *)identifier ctaIdentifier:(NSString *_Nonnull)ctaIdentifier
    {
        NSLog(@"SampleBatchMessagingDelegate - batchMessageDidTriggerAction: %@", messageIdentifier);
        // For MEP messages, ctaIdentifier might be "mepCtaIndex:0", "mepCtaIndex:1", etc.
    }
    
    - (void)batchMessageDidDisappear:(NSString* _Nullable)messageIdentifier reason:(BatchMessagingCloseReason)reason
    {
        switch (reason) {
             case BatchMessagingCloseReasonAction:
                 NSLog(@"SampleBatchMessagingDelegate - batchMessageDidDisappear: %@", messageIdentifier ?: @"<nil>");
                 break;
             case BatchMessagingCloseReasonAuto:
                 NSLog(@"SampleBatchMessagingDelegate - batchMessageWasCancelledByAutoclose: %@", messageIdentifier ?: @"<nil>");
                 break;
             case BatchMessagingCloseReasonUser:
                 NSLog(@"SampleBatchMessagingDelegate - batchMessageWasCancelledByUserAction: %@", messageIdentifier ?: @"<nil>");
                 break;
             case BatchMessagingCloseReasonError:
                 NSLog(@"SampleBatchMessagingDelegate - batchMessageWasCancelledByError: %@", messageIdentifier ?: @"<nil>");
                 break;
         }
    }
    
    @end