All pages
Powered by GitBook
1 of 41

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

iOS

Prerequisites

Creating an app

Let's get going!

The very first step is to go to Batch's dashboard and create a new iOS app.

Registering a new app

Configuring notifications

Once your app is created, you will need to download a .p8 file and upload it to Batch's Dashboard.

As a reminder, Batch servers need to have a valid certificate in order to communicate with Apple Push Notification Services (APNS). There are two types of files you can use:

  • .p8 files (recommended) : Valid for all the apps added to your Apple developer account. You will need to specify the Application Identifier (App ID) or the Bundle ID of your app on Batch's dashboard.

  • .p12 certificates: Generated for a unique App ID and are only valid for one year. If you need a .p12 certificate, please follow these instructions: .

Step 1. Downloading the .p8 file

Head to the , then go to the "Keys" menu and add a new key.

Give a name to your key, tick the "Apple Push Notifications Service" box (1) and press "Configure" (2).

There, in the "Environment" dropdown, select "Sandbox & Production".

Click "Save" and continue until you can download the P8 key. Write down your Key ID.

Step 2. Uploading the .p8 file

Now go to Batch's dashboard → ⚙ Settings → Push settings, and upload your .p8 file.

There are two IDs you need to find to save your certificate and send notifications to your app:

  • App ID / Bundle ID: We recommend you use the bundle ID you will find in Xcode. You can also use the app ID available from the Developer Console .

  • Team ID: The team ID is also available from the Developer Console . It is also shown under your name in the top bar of the place you created your p8 key.

  • Key ID: The Key ID should be automatically filled if you didn't rename the p8 file you downloaded from Apple. If it was not, use the Key ID you have written down earlier. You can also find the Key ID in the Apple Developer Member center, where you generated it.

License Attribution

As Batch uses open-source software, you must include attribution text in your application. This usually goes in a "Legal Notices" or "About" screen.

You can get the attribution text .

Generating a .p12 certificate
Apple Developer Member center
here
here
here
Adding a key
Configuring the key
Configuring the environment
Uploading your certificate

SDK Integration

Migration Guides

Custom region/language

Batch allows you to set a custom region or a custom language that will override the value detected by the SDK. By default, Batch collects the language and the country of your users' devices.

Setting a custom region/language is useful if:

  • You don't want to use the values detected by Batch.

  • Your users can choose their own language or region for localization purposes.

Custom locale

Writing custom region/language

If you call editor method before startWithAPIKey it will return nil. You should always call it after you started the SDK, and check nullity to be safe.

Reading custom region/language

Keep in mind that the data returned is only about your installation and not your Profile since it may be updated from another source.

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:

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

  • User natives (language/region) and custom data will be automatically migrated to a Profile.

These migrations are triggered when your application on the dashboard is attached to a Project. Concretely this mean that there's two possible scenarios:

BatchProfile.editor { editor in
    try? editor.setLanguage("en") // Language must be 2 chars, lowercase, ISO 639 formatted
    try? editor.setRegion("US") // Region must be 2 chars, uppercase, ISO 3166 formatted
    // If you prefer to use BatchProfile.editor() with local variable instead of usiong a closure,
    // remember to use editor.save() afterwards so that the changes are taken into account.
}

Profile Data

[BatchProfile editWithBlock:^(BatchProfileEditor * _Nonnull editor) {
    [editor setRegion:@"en" error:nil]; // Language must be 2 chars, lowercase, ISO 639 formatted
    [editor setRegion:@"US" error: nil]; // Region must be 2 chars, uppercase, ISO 3166 formatted
    // If you prefer to use [BatchProfile editor] instead of `editWithBlock`,
    // remember to use [editor save] afterwards so that the changes are taken into account.
}];
// This is how you retrieve your custom values. Values are nil by default.
let language = BatchUser.language()
let region = BatchUser.region()
// This is how you retrieve your custom values. Values are nil by default.
NSString *language = [BatchUser language];
NSString *region = [BatchUser region];

Your app is running on the SDK v1 and is already attached to a Project : migrations will be triggered on the first start running on SDK v2.

  • Your app is running on the SDK v2 but not attached to a Project: migrations will be triggered on the first start after 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:

    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;
    }

    Advanced

    Testing your integration

    Batch provides a simple debug tool that allows you to test your custom data integration (⚙ Settings → Debug). It shows all the data available for a specific user ID:

    • Token: Find the token of a specific device.

    • Custom user ID: Check if your custom user ID implementation is working correctly.

    • Native attributes: See the installation date, last session date, country and more information on a specific user / install.

    • Custom data: See if your app is tagged properly and if custom attributes/events are sent to Batch.

    Finding your ID

    Installation ID

    You can find the Installation ID of a device by calling:

    You can either log this to the debug logs, or display it in a debug menu directly in your app. It can safely be exposed to your end users, who can then give it back to you if they are experiencing push trouble with their app.

    Starting with Batch 1.18, the SDK will also automatically copy the installation ID to the clipboard if a user follows a .

    IDFA

    You can use to find your Advertising Identifier (IDFA). Just install the app on your device, tap on the share button and copy the Advertising identifier.

    Understanding the results

    The results displayed in the debug tool are updated in real time. There are five different pieces of information shown for a single ID:

    • Basic information (token, install ID, custom user ID, etc).

    • prefixed with b. (b.attribute_name).

    • prefixed with c. (c.attribute_name).

    Here are some details on the native attributes you will find in the results:

    • b.app_version: Version of the app installed on your user's device.

    • b.carrier_code: (Not supported on iOS 16.4+) Your user's carrier. The code provided by Batch is a combination of the Mobile country code (MCC) and the Mobile network code (MNC). The full list of MCC and MNC codes is .

    • b.city_code: City detected by Batch using IP geolocation.

    Event dispatchers

    In this article, we'll go through the process of improving your external analytics by either installing a premade event dispatcher or making your own one.

    What is an event dispatcher?

    Batch can trigger with a number of analytics-oriented events that might be of interest in external analytic tools. An event dispatcher is the code that listens to those events, and dispatches them to the solution of your choice.

    Add a "ready-to-go" dispatcher

    Batch provide you with premade dispatchers for specific use cases (aka ready-to-go dispatcher). They are enabled by simply registering them in your AppDelegate, and will automatically track events into your analytics solution with a predetermined structure.

    Create a custom dispatcher

    Or, if you need, you can implement your own dispatcher.

    Access data from the event

    The Batch SDK will inform you when one of the following event happen:

    • Push notification displayed : Triggered when a push notification is displayed (only available on Android).

    • Push notification clicked : Triggered when a push notification is clicked.

    • Push notification dismissed : Triggered when a push notification is dismissed (only available on Android).

    • Message showed : Triggered when an in-app or landing message appear on the screen.

    With every event comes a BatchEventDispatcherPayload object, allowing you to access data associated with the event, such as a push notification custom payload value, or the tracking ID of an in-app or landing campaign.

    SMS subscription

    The Android Batch SDK allows you to:

    • Add and remove phone number from a user profile. The profile will automatically be created if needed.

    • Edit a profile's marketing subscription for the SMS channel.

    In order to set a phone number and subscription status on a profile, the creation of a project is required. If no project is set up, any calls made for these actions will be ignored.

    Here is how to set a phone number with a marketing subscription:

    Note:

    • The phone number must be an formatted string starting with a + and not longer than 15 digits without any special characters. It should match the following regular expression : ^\+[0-9]{1,15}$

    • Remember if you call editor method before startWithAPIKey it will return nil. You should always call it after you started the SDK, and check nullity to be safe.

    Initial setup

    Integrating the SDK

    Native SDK

    The iOS SDK is a standard mergeable dynamic library, compatible with iOS 15.0 and higher (iOS 16.0 or higher for Mac Catalyst only). Xcode 26.1+ is required, but Batch will work with all Swift versions, along with Objective-C.

    It supports the following architectures:

    Custom user ID

    Batch attaches SDK's installations to a Profile and centralize data and events from multiple sources (Apps, Websites, APIs) in a single place based on a Custom user ID. This allows Batch to support more complex and cross-channel lifecycles: it is now possible to message a user on a channel even though its profile data is fed from another one.

    This allows you to use the .

    This custom user identifier can be:

    • The unique ID you are using in your login system.

    • A stable ID used in your data store (e.g. Firebase, etc).

    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

    Email subscription

    The iOS Batch SDK allows you to:

    • Add and remove email address from a user profile. The profile will automatically be created if needed.

    • Edit a profile's marketing subscription.

    In order to set an email address and subscription status on a profile, the creation of a project is required. If no project is set up, any calls made for these actions will be ignored.

    E.164
    // This requires to have a custom user ID registered by calling the `identify` method beforehand.
    BatchProfile.editor { editor in
      try? editor.setPhoneNumber("+33123456789") // Nil to erase. A valid E.164 phone number.
      editor.setSMSMarketingSubscriptionState(.subscribed) // or .unsubscribed
       // If you prefer to use BatchProfile.editor() with local variable instead of using a closure,
       // remember to use editor.save() afterwards so that the changes are taken into account.
    }
    // This requires to have a custom user ID registered by calling the `identify` method beforehand.
    [BatchProfile editWithBlock:^(BatchProfileEditor * _Nonnull editor) {
        NSError *error = nil;
        [editor setPhoneNumber:@"+33123456789" error:&error]; // Nil to erase. A valid E.164 phone number.
        [editor setSMSMarketingSubscriptionState:BatchSMSSubscriptionStateSubscribed]; // or BatchSMSSubscriptionStateUnsubscribed
         // If you prefer to use [BatchProfile editor] instead of `editWithBlock`,
         // remember to use [editor save] afterwards so that the changes are taken into account.
    
    }];

    Data Privacy

  • Any stable information that can help you to identify a user (e.g. hashed email address, etc).

  • Pushing a custom user identifier will send a notification to every installation with that identifier set.

    If you want to only push a specific installation (and thus a specific device), you should set an installation-specific identifier or use Batch installation ID.

    Identifying a user

    The API is pretty straightforward and MUST be called when Batch is started.

    You should remove the identifier anytime the identified user logs out.

    User identifiers are case and whitespace sensitive. If you want to avoid potential issues, you may want to call lowercaseString and stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] on the identifier string.

    User identifiers must be shorter than 1024 characters.

    Make sure the identifier is unique, otherwise users might get pushes that are not meant for them. You should never associate a custom ID to more than one user.

    Choosing a unique identifier

    If you are wondering which ID you should use as a custom user identifier, here are a few examples:

    Login system

    If you have a login system, you probably have a unique user ID associated to the user's account. Simply set this ID as your user identifier, and don't forget to reset it (by setting it to nil) when the user logs out.

    While email addresses are an appropriate identifier, we suggest you hash them before setting them as your identifier, for privacy reasons.

    PaaS datastore

    Your datastore probably has a stable ID associated to your installation. You just need to make sure it is unique between installations, and you are easily able to retreive it from your backend code.

    Specific backend and database

    Anything that can identify a user uniquely will work. Email address, phone number, etc. We suggest you hash this information before setting it, and use the same hash on your backend.

    Otherwise, you will need to create a stable identifier for your app installation and send it to your backend. The most common way to do that is to generate a RFC4122 UUID.

    Profile API
    Profiles

    Tags prefixed with t. (t.tag_name).

  • Events prefixed with e.(e.event_name).

  • b.device_brand: Brand of your user's device.

  • b.device_type: Model of your user's device. See here if you need more information on a specific iOS/Android device model.

  • b.install_date: Install date of the app (UTC/GMT).

  • b.language: Language of your user.

  • b.last_push_date: Date of the last sent notification from the dashboard or the Campaigns API (UTC/GMT).

  • b.last_visit_date: Date of the last session (UTC/GMT).

  • b.os_version: Version of the OS used on your user's device.

  • b.region: Country of your user.

  • b.transaction_tracked: Your user has or hasn't made any purchases in your app. Make sure you are sending data on transactions to see this information.

  • b.is_push_optin: True if the install is opt-in to push, meaning that it can receive and display push notifications

  • backgrounding/foregrounding sequence
    The Identifiers app
    Native attributes
    Custom attributes
    available here
    Debug tool

    Message closed : Triggered when an in-app or landing message is explicitly closed by the user (using the close button or a dismiss CTA/Global tap).

  • Message auto-closed : Triggered when an in-app or landing message is closed by the auto-close timer.

  • Message clicked : Triggered when an in-app or landing Click-To-Action is clicked or when the in-app/landing is global tapped (except if the CTA/Global Tap is a dismiss, then a closed event is triggered).

  • Custom
    Cover

    Firebase

    Cover

    Piano Analytics

    Cover

    AT Internet

    Cover

    Mixpanel

    BatchProfile.identify("custom_user")
    [BatchProfile identify:@"custom_user°id"];
    BatchProfile.identify(nil)
    [BatchProfile identify:nil];
    BatchUser.installationID()
    [BatchUser installationID];
    func dispatchEvent(with type: BatchEventDispatcherType,
    payload: BatchEventDispatcherPayload) {
        NSLog("Dispatcher: I'm dispatching an event with ID: %@", payload.trackingId);
    }
    - (void)dispatchEventWithType:(BatchEventDispatcherType)type 
                          payload:(nonnull id<BatchEventDispatcherPayload>)payload {
        NSLog(@"Dispatcher: I'm dispatching an event with ID: %@", payload.trackingId);
    }
    x86_64
  • arm64 (both for iOS, and iOS Simulator on Apple Silicon)

  • Catalyst is supported.

    The iOS SDK also supports visionOS in a restricted usage since:

    • In-App messaging and mobile landings are unavailable.

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

    Integrating with:

    Use Xcode's Swift Package Manager wizard to add

    Package.swift
    https://www.github.com/BatchLabs/Batch-iOS-SDK.git

    CocoaPods 1.15 required

    First, simply add this line to your Podfile to integrate Batch in your project:

    Podfile
    pod 'Batch', '~> 3.1'

    Then, run pod install in your Podfile folder, and open the .xcworkspace that was created. You're ready to go! In order to update Batch SDK, simply run pod update in that directory.

    If you don't have a Podfile or are unsure on how to proceed, see the CocoaPods usage guide.

    Note: Due to CocoaPods limitations, the Batch Pod is not a mergable library and has no code signing. If you need one of those, please use SPM or manually integrate the XCFramework.

    Batch is only available on Carthage 0.30 and higher

    Simply add this line to your Cartfile to integrate Batch in your project:

    Do not add Batch to the "carthage copy-frameworks" script input/output.

    XCFramework distribution is supported since Batch 1.18.0 and Carthage 0.38.

    Apple now requires Privacy Manifest that's not available with Carthage.

    Your first start

    Implement the Batch startWithAPIKey: method in your AppDelegate application:didFinishLaunchingWithOptions: method:

    If you're making a SwiftUI app, you will need to add a delegate first.

    YOUR_API_KEY is your SDK API Key. You'll find it in ⚙ Settings → General.

    This API key must not be mistaken for the APNS environment Batch will output to the Xcode logs when starting. The environment depends on the app's provision, not on the Batch API Key used.

    Testing your integration

    Congratulations on finishing the bulk of the integration!

    If you want to validate your implementation before proceeding with the Push setup, you can locate the log that Batch posts in the Xcode console.

    Batch also provides a simple debug tool that allows you to test your integration (⚙ Settings → Debug) and a profile view that shows all the data available for a specific user ID.

    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 Apple Developper website).

    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.

    class AppDelegate: UIApplicationDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            BatchSDK.associatedDomains = ["example.com"
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        [BatchSDK setAssociatedDomains:@[@"example.com", @"www.example.com"
    

    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 Apple Developer website).

    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.

    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;

    Here is how to set an email with a marketing subscription:

    // This requires to have a custom user ID registered by calling the `identify` method beforehand.
    BatchProfile.editor { editor in
      try? editor.setEmailAddress("john.doe@batch.com") // Nil to erase. Addresses must be valid.
      editor.setEmailMarketingSubscriptionState(.subscribed) // or .unsubscribed
       // If you prefer to use BatchProfile.editor() with local variable instead of using a closure,
       // remember to use editor.save() afterwards so that the changes are taken into account.
    
    // This requires to have a custom user ID registered by calling the `identify` method beforehand.
    [BatchProfile editWithBlock:^(BatchProfileEditor * _Nonnull editor) {
        NSError *error = nil;
        [editor setEmailAddress:@"john.doe@batch.com" error:&error]; // Nil to erase. Addresses must be valid.
        [editor setEmailMarketingSubscriptionState:BatchEmailSubscriptionStateSubscribed]; 
    

    If you call editor method before startWithAPIKey it will return nil. You should always call it after you started the SDK, and check nullity to be safe.

    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!

    Mixpanel

    The Mixpanel dispatcher automatically dispatches events to the Mixpanel SDK, using UTM tags when available.

    Two flavors of the dispatcher are available, depending on which flavour of the Mixpanel SDK you're using:

    • One for the Objective-C Mixpanel SDK

    • One for the Swift Mixpanel SDK

    "https://github.com/BatchLabs/Batch-iOS-mixpanel-swift-dispatcher"
    Podfile
    pod 'BatchMixpanelSwiftDispatcher'
    # or
    # pod 'BatchMixpanelObjcDispatcher'
    Cartfile
    github "BatchLabs/Batch-iOS-mixpanel-swift-dispatcher"
    # or
    # github "BatchLabs/Batch-iOS-mixpanel-objc-dispatcher"

    Don't forget to register the dispatcher:

    Once your dispatcher is installed, restart your app and you should see something like this in your log:

    If you can't find the line in your log, it may be due to missing dependencies, be sure to add the AT Internet and/or Firebase Analytics SDKs to your project.

    Firebase

    The Firebase dispatcher automatically dispatches events to the Firebase SDK, including UTM tags when they are available.

    https://github.com/BatchLabs/Batch-iOS-firebase-dispatcher.git
    Podfile
    pod 'BatchFirebaseDispatcher'
    github "BatchLabs/Batch-iOS-firebase-dispatcher"

    Don't forget to register the dispatcher:

    @import BatchFirebaseDispatcher
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey
    
    
    // MYAppDelegate.h
    
    @interface MYAppDelegate : UIResponder <UIApplicationDelegate>
    
    [...]
    
    @end
    
    // MYAppDelegate.m
    
    @import BatchFirebaseDispatcher;
    
    @implementation MYAppDelegate
    
    

    Custom

    If for some reason, you need to implement your own dispatcher, you must create a BatchEventDispatcherDelegate and register it to the SDK using [BatchEventDispatcher addDispatcher].

    You don't need to add a dispatcher if you're using one of the previous "ready-to-go" library ! It will be automatically registered.

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate, BatchEventDispatcherDelegate {
        
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]
    
    
    // MYAppDelegate.h
    
    @interface MYAppDelegate : UIResponder <UIApplicationDelegate,
                                            BatchEventDispatcherDelegate>
    
    [...]
    
    @end
    
    // MYAppDelegate.m
    
    @implementation MYAppDelegate
    
    
    

    If the Batch SDK happens to be opt-out from, dispatchers won't receive any events.

    AT Internet

    The AT Internet dispatcher automatically dispatches events to the AT Internet SDK, including the XTOR tag when it's available.

    This dispatcher should not be used in new integrations in favor of Piano Analytics

    github "BatchLabs/Batch-iOS-atinternet-dispatcher"
    Podfile
    pod 'BatchATInternetDispatcher'

    Then, register the dispatcher:

    import BatchATInternetDispatcher
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey
    
    
    // MYAppDelegate.h
    
    @interface MYAppDelegate : UIResponder <UIApplicationDelegate>
    
    [...]
    
    @end
    
    // MYAppDelegate.m
    
    @import BatchATInternetDispatcher;
    
    @implementation MYAppDelegate
    
    

    Batch AT Internet Dispatcher 1.1.0+ will automatically instantiate Tracker instances using the default AT Internet configuration, which comes in a plist. If you're not using plist-based configuration and would like to manually configure your Tracker in code, you can tell Batch which tracker instance to use:

    SDK opt-out (e.g. GDPR)

    If you plan to send personal user data and have to comply with EU's GDPR, any other data privacy law or simply want to give your users more control regarding their privacy, these methods will help you plug your consent screen to Batch.

    There are two SDK operation modes:

    • SDK enabled by default, opting-out required via an explicit method call: this is the default mode.

    • SDK disabled by default, opting-in required

    import Batch
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        BatchSDK.start(withAPIKey: "YOUR_API_KEY")
        [..]
    }
    @import Batch;
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
      // Start Batch SDK.
      [BatchSDK startWithAPIKey:@"MY_API_KEY"];
    
      return YES;
    }
    import SwiftUI
    import Batch
    
    
    class MyAppDelegate: NSObject, UIApplicationDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            BatchSDK.start(withAPIKey: "YOUR_API_KEY")
    
            return true
        }
    }
    
    @main
    struct BatchApp: App {
        // Set an explicit delegate to integrate Batch in
        @UIApplicationDelegateAdaptor(MyAppDelegate.self) var delegate
    
    
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    [Batch] Installation ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
    ,
    "www.example.com"
    ]
    // ...
    }
    }
    ]
    ];
    // ...
    }
    }
    // or BatchEmailSubscriptionStateUnsubscribed
    // If you prefer to use [BatchProfile editor] instead of `editWithBlock`,
    // remember to use [editor save] afterwards so that the changes are taken into account.
    }];
    :
    Any
    ]
    ?
    )
    ->
    Bool
    {
    BatchEventDispatcher.add(BatchFirebaseDispatcher.instance())
    [...]
    BatchSDK.start(withAPIKey: "YOUR_API_KEY")
    return true
    }
    }
    - (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    [BatchEventDispatcher addDispatcher:[BatchFirebaseDispatcher instance]];
    [...]
    [BatchSDK startWithAPIKey:@"MY_API_KEY"];
    return YES;
    }
    @end
    ?
    )
    ->
    Bool
    {
    BatchSDK.start(withAPIKey: "YOUR_API_KEY")
    [...]
    BatchEventDispatcher.add(self)
    return true
    }
    func dispatchEvent(with type: BatchEventDispatcherType,
    payload: BatchEventDispatcherPayload) {
    NSLog("Dispatcher: I'm dispatching an event");
    }
    }
    - (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    [BatchSDK startWithAPIKey:@"MY_API_KEY"];
    [...]
    [BatchEventDispatcher addDispatcher:self];
    return YES;
    }
    - (void)dispatchEventWithType:(BatchEventDispatcherType)type
    payload:(nonnull id<BatchEventDispatcherPayload>)payload {
    NSLog(@"Dispatcher: I'm dispatching an event");
    }
    @end
    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
    github "BatchLabs/Batch-iOS-SDK"

    On iOS, disabling the SDK won't disable push notifications if Batch already has a valid token. In case you want to implement a notification opt-out, you will need to direct users to iOS' settings by using the [BatchPush openSystemNotificationSettings](Objective-C) or the BatchPush.openSystemNotificationSettings() (Swift) method.

    Opting-out

    In order to opt-out from SDK, simply call:

    BatchSDK.optOut()
    [BatchSDK optOut];

    Opting out will:

    • Prevent [BatchSDK startWithAPIKey:] from starting the SDK

    • Disable any network capability from the SDK

    • Disable all In-App campaigns

    • Make the Inbox module return an error immediately

    • Make any call to -[BatchProfileEditor save] do nothing

    • Make any "tracking" methods from BatchProfile ineffective

    Even if you opt-in afterwards, data generated (such as user data or tracked events) while opted out WILL be lost.

    You can also wipe the SDK installation data:

    This will wipe the data locally and request a remote data removal for the matching Installation ID and its associated data. Any user-level data associated with the Custom User ID (if any) set via our server APIs will not be deleted. However, the Installation ID <> Custom User ID association will be, effectively logging the user out before deleting their installation data. Sending notifications to this user using their Advertising ID (via the Transactional API or a Custom Audience) will not be possible for a month.

    Opting out is not possible while the SDK hasn't started. Make sure you've called [BatchSDK startWithAPIKey:] beforehand.

    Disabling the SDK by default

    In order to disable the SDK by default, you need to add a key called BATCH_OPTED_OUT_BY_DEFAULT with a true boolean value to your Info.plist.

    Opting-in

    Once you've opted-out (either programatically, or by default), you can re-enable the SDK by calling the following:

    Even if you've already done so before opting-out, you are required to call [BatchSDK startWithAPIKey:] or the SDK won't work until the next start.

    What are the impacts of disabling the SDK?

    Disabling Batch SDK by default

    You can disable Batch SDK by default and start it when you need it, later in the user journey. This is helpful if you are using a Consent Management Platform (CMP) and don't want to start collecting data before obtaining your users' consent.

    Batch will only detect in the analytics and be able to contact users who started the SDK once. The rest of your users won't be detected by Batch. You won't be able to reach them using push notifications or In-App messages.

    Allowing users to disable Batch SDK in your app settings

    If you want to give your users more control regarding their privacy, you can give them the option to disable Batch SDK in the app settings.

    A) Disabling the SDK

    Batch SDK will not start anymore. Here are the impacts of the SDK deactivation:

    • Analytics: Your users will no longer be detected by Batch when they open the app or open a notification.

    • Data collection: Batch will stop collecting native and custom data for these installs.

    • Push notifications: iOS users opt-in to push notifications will keep receiving alerts because they are displayed directly by the OS and their push token remains valid even after Batch SDK opt-out. They will need to disable push notifications manually from iOS system settings if they want to stop receiving alerts. On Android, Batch SDK receiver is in charge of receiving and displaying push notifications. Disabling Batch SDK will also disable push notifications.

    • In-App messaging: Your users won't see any In-App messages. In-App messages are displayed by Batch SDK.

    • Inbox: The content of your notification centre won't be updated if you rely on Batch Inbox.

    If users re-enable Batch SDK later from your app settings, analytics, data collection, push notifications, In-App messages and the Inbox feature will work again.

    B) Disabling the SDK and wiping the data

    In addition to disabling the SDK, you can also send a request to delete the data attached to the installation. This will wipe the data locally and request a remote data removal for the matching Installation ID. You should only use that method if you are fully aware of the impacts on your users.

    Batch will blacklist the advertising ID attached to the installation for one month following the data removal.

    import Batch
    import BatchMixpanelSwiftDispatcher
    // or import BatchMixpanelObjcDispatcher
    import Mixpanel
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            // Find your mixpanel setup line, which looks like this:
            let mixpanel = Mixpanel.initialize(token: "MIXPANEL_TOKEN")
            // Objective-C Mixpanel SDK users: let mixpanel = Mixpanel.sharedInstance(withToken: "MIXPANEL_TOKEN")
    
            let mixpanelDispatcher = BatchMixpanelSwiftDispatcher.instance
            // If you're using BatchMixpanelObjcDispatcher, comment the previous lines and uncomment the next one
            // let mixpanelDispatcher = BatchMixpanelObjcDispatcher.instance()
             
            // Tell Batch about your Mixpanel instance to enable tracking
            mixpanelDispatcher.mixpanelInstance = mixpanel
            BatchEventDispatcher.add(mixpanelDispatcher)
    
            [...]
    
            BatchSDK.start(withAPIKey: "YOUR_API_KEY")
            return true
        }
    }
    :
    Any
    ]
    ?
    )
    ->
    Bool
    {
    BatchEventDispatcher.add(BatchATInternetDispatcher.instance())
    [...]
    BatchSDK.start(withAPIKey: "YOUR_API_KEY")
    return true
    }
    }
    - (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    [BatchEventDispatcher addDispatcher:[BatchATInternetDispatcher instance]];
    [...]
    [BatchSDK startWithAPIKey:@"MY_API_KEY"];
    return YES;
    }
    @end

    Piano Analytics

    The Piano Analytics dispatcher automatically dispatches events to the Piano SDK, including AT tags when they are available.

    https://github.com/BatchLabs/Batch-iOS-piano-dispatcher.git
    Podfile
    pod 'BatchPianoDispatcher'
    Cartfile
    github "BatchLabs/Batch-iOS-piano-dispatcher"

    By default the dispatcher will handle UTM tracking and will send events only as Piano On-site Ads events.

    Register the dispatcher and update the configuration as following:

    If you enable custom events, you also need to define them in your Piano Data Model. If so, please refer to the custom event/property list.

    BatchSDK.optOutAndWipeData()
    [BatchSDK optOutAndWipeData];
    BatchSDK.optIn()
    BatchSDK.start(withAPIKey: "YOUR_API_KEY")
    BatchPush.refreshToken();
    [BatchSDK optIn];
    [BatchSDK startWithAPIKey:@"YOUR_API_KEY"];
    [BatchPush refreshToken];
    // MYAppDelegate.m
    
    @import Batch;
    @import BatchMixpanelObjcDispatcher;
    @import Mixpanel;
    
    @implementation MYAppDelegate
    
    - (BOOL)application:(UIApplication *)application 
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // Find your mixpanel setup line, which looks like this:
        Mixpanel *mixpanel = [Mixpanel sharedInstanceWithToken:@"MIXPANEL_TOKEN"];
        
        // BatchMixpanelSwiftDispatcher isn't supported from Objective-C
        BatchMixpanelObjcDispatcher *mixpanelDispatcher = [BatchMixpanelObjcDispatcher instance];
        // Tell Batch about your Mixpanel instance to enable tracking
        mixpanelDispatcher.mixpanelInstance = mixpanel;
        [BatchEventDispatcher addDispatcher:mixpanelDispatcher];
    
        [...]
    
        [BatchSDK startWithAPIKey:@"MY_API_KEY"];
        return YES;
    }
    
    @end
    [Batch] EventDispatcher - Adding event dispatcher: BatchFirebaseDispatcher
    // Put this right after BatchEventDispatcher.add(BatchATInternetDispatcher.instance())
    let tracker = <your ATInternet tracker initialization code>
    BatchATInternetDispatcher.instance().trackerOverride = tracker
    // Put this right after [BatchEventDispatcher addDispatcher:[BatchATInternetDispatcher instance]];
    Tracker *myTracker = ...;
    [[BatchATInternetDispatcher instance].trackerOverride = myTracker;
    @import BatchPianoDispatcher
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        let pianoDispatcher = BatchPianoDispatcher.instance
        
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            
            // PA Configuration
            pa.setConfiguration(ConfigurationBuilder()
                .withCollectDomain("logsx.xiti.com")
                .withSite(123456789)
                .build()
            )
            // Uncomment if you want to enable custom event sending (default: false)
            //pianoDispatcher.enableCustomEvents = true
    
            // Uncomment if you want to disable On-Site Ads event sending (default: true)
            //pianoDispatcher.enableOnSiteAdsEvents = false
    
            // Uncomment if you want to disable UTM tracking (default: true)
            //pianoDispatcher.enableUTMTracking = false
            
            BatchEventDispatcher.add(pianoDispatcher)
            
            [...]
    
            BatchSDK.start(withAPIKey: "YOUR_API_KEY")
            return true
        }
    }

    Inbox

    Inbox is an optional feature. If you are interested, please contact us.

    The Inbox API allows you to fetch and process notifications that this user has previously received, even if their device was offline. You can then do anything you want with the data, such as making a "notification center", where the user can catch up with previous notifications in a list.

    The API gives you access to the entire notification, including its raw payload. It also lets you know if the notification has already been read, and allows you to mark one or all notifications as such. Once received/stored in the inbox, your push notifications will remain for a 3 months period.

    This screenshot is an example of what you can achieve with the Inbox API. Please note Batch does not include any UI.

    Inbox iOS example

    Picking the Right Fetcher

    Installation Mode

    This mode will fetch notifications from the current app installation, and nothing else. If the user clears the app's data, this inbox will be cleared. This is great for applications where your users don't have to log in.

    In this mode, you can simply get the Inbox Fetcher with a context:

    Note: This may cause privacy issues if your app has an identification system. Users sharing the same device and using different accounts in your app would be able to see the push history of previous users.

    Custom User ID Mode

    This mode will fetch notifications for the specified , even if they just installed the application and already got notifications on another device logged in with the same user identifier. If your application has a login system, this is the mode you should use.

    Since notifications can have sensitive content, you cannot get a user's notifications simply with their user identifier: Batch requires you to authenticate your request.

    Getting Your Authentication Key

    First, you will need your inbox secret key. You will find that in your dashboard, below your API Keys. It is unique for every app.

    The authentication key is generated by computing a sha256 hmac hash of the API Key concatenated with the user identifier, using the secret as the key. Then, you have to encode the hash in a hexadecimal string.

    For the API Key "abcdef", user identifier "paul" and secret of "foobar", the string to hash is abcdefpaul and the expected authentication key would be 796f1ab5d119d1b2eab8201e60335b56d1befff40c0f80263d64a169a8fd2e45.

    PHP example code:

    Note: This hash HAS to be computed on your server. If you bundle the inbox secret in your application to compute the hash, attackers will be able to extract it, and read the notifications of any of your users

    Getting the Fetcher Instance

    Once you've got the authentication key from the server, you only have to give Batch the right user identifier and auth key tuple to get the Inbox Fetcher:

    Fetching Notifications

    Now that you've got your fetcher instance, it's time to fetch notifications, and display them!

    The inbox fetcher has several important methods and properties:

    • allFetchedNotifications This returns a copy of all the notifications that have been fetched. Useful for a list adapter. Warning: Calling this always makes a copy of the objects, so you should cache that method's result.

    • fetchNewNotifications() Allows you to fetch notifications that might have been received after the initial fetch. This is useful for implementing a refresh feature. This will erase the notification history returned by allFetchedNotifications to ensure consistency: you should clear the other notification pages you've already from your cache.

    Note: BatchInboxFetcher and its methods are well documented in BatchInbox.h. You can also use the of BatchInboxFetcher to get detailed explanations about what every method does.

    BatchInboxFetcher will not fetch any notification by default, so before trying to display anything, you will need to call fetchNewNotifications() or fetchNextPage()

    Both fetch methods take a block, which the SDK will call you back on either on failure, or success. Success callbacks include information about the operation to operate on the data, but you can very well do with the global methods. The block callbacks are always called on the main thread.

    Reading Notification Content

    Once you've fetched notifications, you will end up with a list of BatchInboxNotificationContent objects.

    These objects have everything you need to display these notifications:

    • Title

    • Body

    • Send timestamp (UTC)

    • Read state

    Note: Just like when you get it using iOS' standard callbacks, the raw payload (accessible from the payload property) should be used carefully on keys you do not control:

    • "aps" is considered private by Apple, and can change at any time. This has happened in the past, where the "alert" object got additional keys for new iOS features.

    • "com.batch" is Batch's internal payload: You should not make any assumption about its content, nor depend on it. We reserve the right to change it at anytime, without warning.

    • Standard parsing good practices apply: Make sure to check every cast and handle errors gracefully, and never assume anything about the payload's content.

    You can then use the same methods that you can use on push payloads to extract information. Example: BatchPush.deeplink(fromUserInfo: notificationContent.payload), where "notificationContent" is a BatchInboxNotificationContent instance.

    Marking Notifications as Read

    Notifications can be marked as read in two ways:

    • By marking only one notification as read Use markNotification(asRead: notification) with the notification you want to mark as read.

    • By marking all notifications as read Simply call markAllNotificationsAsRead()

    In both cases, the notifications will be marked as read locally, but refreshing them might mark them as unread again, as the server might not have processed the request. These methods return quickly, and are thus safe to call on your UI thread.

    Note that notifications that have been opened when received are automatically marked as read.

    Displaying Mobile Landing

    Batch allows you to display a mobile landing attached to a notification. To do so, you can simply trigger the landing message from the BatchInboxNotificationContent object as following:

    Marking Notifications as Deleted

    Notifications can be deleted using the markNotification(asDeleted: notification) method with the notification you want to mark as deleted. A deleted notification will NOT appear in the notification list the SDK provides, and you will be expected to update your UI accordingly.

    The notifications will be marked as deleted locally, but refreshing them might make them appear again, as the server might not have processed the request. These methods return quickly, and are thus safe to call on your UI thread.

    Tweaking the Fetcher

    For more advanced usages, you can also change various aspects of the Inbox Fetcher, such as:

    • The maximum number of notifications fetched per page (maxPageSize)

    • The maximum number of notifications that can be fetched using this object (limit) A default limit is set to avoid going over memory by accident.

    • 1.19.0 Disabling the filtering of silent notifications (filterSilentNotifications). A silent notification is a push that doesn't show any visible message to the user.

    Testing Your Integration

    In order to test your integration, you will need to create a push campaign on the dashboard. Please see our fore more info.

    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 .

    Automatic data collection

    By default, the Batch SDK will automatically collect some data related to the user's device:

    Native data
    Description
    Default collection status

    Device Model

    The user's device model information

    false

    Installation ID

    The installation identifier

    true

    Session ID

    The user's session identifier

    Some of this data are disabled by default and can be toggled on/off according to the user consent:

    • Device model

    • GeoIP

    Use the following API to fine-tune what you want to enable:

    This API can be used at any time in your application lifecycle and Batch will remember these values, even if your Application reboots.

    Push setup

    Setting up the Push

    First of all, open your project in Xcode by clicking on it in the sidebar, then click on the Signing & Capabilities tab. If Push Notifications isn't already there, click on + Capability and pick Push Notifications. If there is a Fix button shown, press it.

    In-App Messaging

    In-App Campaigns allow you to trigger messages when users open your app or perform a specific action. This is great to communicate with users who have turned off push notifications or to show contextual messages while your users are browsing your app (e.g. special offers, update reminder, etc).

    Displaying In-App messages

    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

    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

    Fully automatic mode

    There is no code required to make In-App Messages work in automatic mode. Create some campaigns on your dashboard, and they will start coming up in your app. If they don't, please head over to the troubleshooting section.

    Disabling Dynamic Type

    Batch adapts textual content to honor the user's font size settings. This is enabled by default, you can disable it at any moment by using:

    Controlling the display using "Do Not Disturb mode"

    You can also get more control on when messages are displayed without giving up on the automatic mode, by using the "Do Not Disturb" (DnD) mode. It allows you to tell Batch to hold on a mobile landing for you, rather than display it without using the fully manual mode. For example, if launching your app results in a splash screen or a fullscreen ad, you might find it undesirable to have Batch display something on top of it.

    Turning on "Do Not Disturb" mode will make Batch enqueue the latest mobile landing, rather than display it.

    Toggling DnD

    Now, when you don't want Batch to automatically display, turn on Do Not Disturb:

    Once you want to start showing landings automatically, call the method with false to turn it off.

    Note: Disabling Do Not Disturb mode does NOT make Batch show the enqueued message

    Displaying pending mobile landings

    After coming back from DnD mode, you might want to show the enqueued message, as Batch will not do that automatically. Batch exposes two properties/methods for managing the queue:

    • BatchMessaging.hasPendingMessage , allowing you to peek into the queue.

    • BatchMessaging.popPendingMessage() , allowing you to fetch the pending message (if any). Since calling this makes Batch delete its reference to it to save memory, further calls might return nil.

    • BatchMessaging.showPendingMessage() , allowing you to try to show the pending message, if any.

    Here is a quick example of how they can be used:

    Note: Only the latest message is queued: if a mobile landing arrives while one is still pending, it will overwrite the previous one.

    Manual mode

    Like Mobile Landings, you may want to take full control over how in-app messaging behaves. Batch allows you to disable automatic displaying, and handle that yourself. For compatibility reasons, BatchMessaging.setAutomaticMode(false) does not control this behavior like it does on mobile landings.

    In order to handle messages yourself, you will have to set an implementation of the BatchInAppDelegate protocol. By implementing this, you opt-in to manual message handling, and showing them becomes your responsibility.

    Here is an example:

    Note: More advanced examples showing how to manually display the given view controller are available in the Mobile Landings manual mode documentation

    Troubleshooting

    In-App messages fail to show

    This can come from multiple things:

    • Batch might not be able to display the message: make sure all of the prerequisites are satisfied by your app. If you have a non standard UI architecture.

    • The In-App campaign might not have been synchronized yet. Try to kill and restart your app: backgrounding it might not be enough.

    • If the trigger is "next session", you might just not have triggered a new session yet. Try killing (as in swiping it up on the task manager) and restarting the app, or wait 5 minutes with the app in the background. Restarting the app might be needed twice: once to sync the campaigns, and one to trigger the "next session" event.

    • If an In-App message is triggered while a view controller is being dismissed (especially a modally presented one), showing the message can fail. You can try displaying it manually, or tracking the event that triggers an in-app message after the dismiss animation.

    If that does not fix the problem, please check the logs in Xcode/Console.app and see if Batch or UIKit outputs anything. If not, you can always contact our support team with information about your problem.

    In-App formats
    fetchNextPage()
    Fetches the next page of notifications. Batch will
    not
    fetch all notifications at once, but only small batches of them in pages. Use this method to load more notifications.
  • endReached Lets you know if more notifications might be available. Use this to make an infinite list, or a "load more" button.

  • Source (Campaign API/Dashboard or Transactional API)
  • Rich notification attachment URL

  • Raw payload (like userInfo in Apple's callbacks)

  • custom user ID
    web API Reference
    guide
    Inbox secret dashboard screenshot
    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)
    }

    true

    Custom User ID

    The user's custom identifier

    true

    API Level

    The Batch SDK api level

    true

    Messaging API Level

    The Batch SDK Messaging api level

    true

    Device Language

    The user's device language

    true

    Device Region

    The user's device region

    true

    Device Timezone

    The user's device timezone

    true

    OS Version

    The user's device iOS version

    true

    App Version (String)

    The application version (string)

    true

    App Version (Build Number)

    The application version (number)

    true

    Bundle Name

    The application bundle identifier

    true

    Device Date

    The current device date

    true

    Device Installation Date

    The Batch installation date

    true

    Bridge Version

    Version of the batch bridge (for cross-platform plugin only)

    true

    Plugin Version

    Version of the batch plugin (for cross-platform plugin only)

    true

    GeoIP

    Whether Batch should resolve the user's location from ip address

    false

    BatchMessaging.setEnableDynamicType(false)
    [BatchMessaging setEnableDynamicType:false];
    BatchMessaging.doNotDisturb = true
    BatchMessaging.doNotDisturb = YES;
    func splashScreenDidDisappear() {
      BatchMessaging.doNotDisturb = false
      BatchMessaging.showPendingMessage()
    }
    - (void)splashScreenDidDisappear {
      BatchMessaging.doNotDisturb = NO;
      [BatchMessaging showPendingMessage];
    }
    
    // Application delegate
    
    var inAppMsgDelegate = InAppMsgDelegate()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      BatchMessaging.setInAppDelegate(inAppMsgDelegate)
    }
    
    // InAppMsgDelegate implementation
    
    @objc
    class InAppMsgDelegate: NSObject, BatchInAppDelegate {
        func batchInAppMessageReady(message: BatchInAppMessage) {
            // Sample implementation that prevents messages from being displayed if the logged in user isn't the right one
            // "loggedInUsername" is an hypothetical property that returns the logged in username as a string, if logged in
            if (UserManager.loggedInUsername == message.customPayload?["username"] as? String) {
                do {
                    let vc = try BatchMessaging.loadViewController(for: message)
                    BatchMessaging.present(vc)
                } catch let error as NSError {
                    // Handle the error
                }
            }
        }
    }
    
    // Application delegate
    
    @property InAppMsgDelegate *inAppMsgDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
      self.inAppMsgDelegate = [InAppMsgDelegate new];
      [BatchMessaging setInAppDelegate:self.inAppMsgDelegate];
    }
    
    // InAppMsgDelegate implementation
    
    @interface InAppMsgDelegate : NSObject <BatchInAppDelegate>
    @end
    
    @implementation InAppMsgDelegate
    
    - (void)batchInAppMessageReady:(nonnull BatchInAppMessage*)message
    {
      // Sample implementation that prevents messages from being displayed if the logged in user isn't the right one
      // "loggedInUsername" is an hypothetical property that returns the logged in username as a string, if logged in
      if ([[UserManager loggedInUsername] isEqualToString:message.customPayload["username"]])
      {
        NSError *err;
        UIViewController *vc = [BatchMessaging loadViewControllerForMessage:message error:&err];
        if (vc)
        {
          [BatchMessaging presentMessagingViewController:vc];
        }
        else
        {
          // Handle the error
        }
      }
    }
    
    @end
    
    let inboxFetcher = BatchInbox.fetcher()
    BatchInboxFetcher* inboxFetcher = [BatchInbox fetcher];
    $APP_API_KEY = "abcdef";
    $USER_ID = "paul";
    $INBOX_SECRET = "foobar";
    $HASHED_STRING = $APP_API_KEY . $USER_ID; // "abcdefpaul". Note that . is the PHP concatenation operator. In another language, it usually is +
    hash_hmac("sha256", $HASHED_STRING, $INBOX_SECRET) // Returns "796f1ab5d119d1b2eab8201e60335b56d1befff40c0f80263d64a169a8fd2e45"
    let inboxFetcher = BatchInbox.fetcher(forUserIdentifier: "paul", authenticationKey: "796f1ab5d119d1b2eab8201e60335b56d1befff40c0f80263d64a169a8fd2e45")
    BatchInboxFetcher* inboxFetcher = [BatchInbox fetcherForUserIdentifier:@"paul" authenticationKey:@"796f1ab5d119d1b2eab8201e60335b56d1befff40c0f80263d64a169a8fd2e45"];
    func notificationClicked(notification: BatchInboxNotificationContent) {
      if (notification.hasLandingMessage) {
          notification.displayLandingMessage()
      }
    }
    - (void)notificationClicked:(BatchInboxNotificationContent *)notification {
        if (notification.hasLandingMessage) {
            [notification displayLandingMessage];
        }
    }
    
    - (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);
    }
    BatchSDK.updateAutomaticDataCollection { config in
        // Enable GeoIP resolution on server side
        config.setGeoIPEnabled(true)
        // Enable automatic collection of the device model information
        config.setDeviceModelEnabled(true)
    }
    [BatchSDK updateAutomaticDataCollection:^(BatchDataCollectionConfig * _Nonnull config) {
      // Enable GeoIP resolution on server side
      [config setGeoIPEnabled:true];
      // Enable automatic collection of the device model information
      [config setDeviceModelEnabled:true];
    }];

    Then, you need to add a few lines to your app delegate in order to receive push notifications.

    Note: If you already implement an UNUserNotificationCenterDelegate class, please read the intercepting notifications documentation to properly integrate Batch into it.

    Requesting notification permission

    To ask for the permission to display notifications and register the current device for push you can use one of the following APIs:

    Note: It's preferable to make the request in a context that helps people understand why your app needs authorization.

    Your first notification

    1. Obtaining your device token

    You can find your device's token using the debug tool or locating the token Batch posts to the Xcode console:

    Based on your Provisioning Profile, the token shown in the console will be Development ("Sandbox/Development") or Production ("Production").

    If you don't see a push token, there might be an error in the logs describing what happened. See our troubleshooting documentation for more info.

    Note: Push notifications in an iOS Simulator are only supported when using an iOS 16 or higher simulator on macOS 13 on a T2 or Apple Silicon mac. Older simulators, macOS versions or computers do not support Push in the simulator: please use a physical device.

    2. Obtaining your Installation ID

    You can then retrieve the Installation ID, which represents an installation of your app on a device, by calling the following methods:

    While Batch prints this in the debug console on start, displaying it in a settings or about page enables users to send you this identifier. This is useful for debugging, sending test notifications, etc.

    3. Sending a test push notification

    Batch enables you to send a test notification to the application installation currently running on your device.

    To do so, open the dashboard and go to ⚙ Settings → Debug. Enter your Installation ID, hit Debug and then click on "Send Test Push".

    Send Test from Debug

    You should receive a notification on your device. If not, or if you can't find your Installation ID, the SDK might not be properly configured.

    If you need to send push notifications to your device on a regular basis, then you should add your ID as a test device by clicking the "Save as a test device" button.

    Troubleshooting If you're having trouble sending test notifications, you can check our troubleshooting documentation.

    What's next

    Congratulations on finishing the integration of Batch Push!

    Here are a couple of extra steps you can take before releasing your app:

    • Rich notifications: Add support for iOS rich push notifications.

    • Mobile Landings: Make sure Mobile Landings are set up correctly.

    • Custom user identifier: Add support for custom user identifiers if you are planning to use the Transactional or the Profile APIs.

    • Analytics: Add an to automatically track your campaigns in your third-party analytics tool.

    • Universal Links: domains to Batch to open then inside of your app.

    Xcode Capabilities
    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 that guide 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

    • Set it as the default UNUserNotificationCenter's delegate

    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.

    located here

    Attributes

    In addition of setting a custom user ID, an email address or overriding the language/region, you can assign attributes to your users, allowing you to improve your orchestrations targeting.

    Profile attributes

    Important:

    User IDs must be managed using our custom user ID implementation.

    Email address must be managed using our email subscription implementation.

    Region/language data must be managed using our custom region/language implementation.

    Never use an existing tagging plan.

    Newly tracked attributes are hidden by default. You will need to manually display them from the dashboard settings.

    Managing attributes

    Before we get started on how to implement attributes, here are some rules you should know.

    Naming

    Attribute names are strings. They should be made of letters, numbers or underscores ([a-z0-9_]) and can't be longer than 30 characters (e.g. has_premium).

    Values

    Values must be any of the following types, or their native Swift equivalent:

    NSString Must not be longer than 300 characters and cannot be empty. For better results, you should make them upper/lowercase and trim the whitespaces.

    NSNumber

    Anything bigger than a long long or a double will be rejected.

    Unsigned values will be rejected.

    Booleans are supported, but should be initialized with [NSNumber numberWithBool:<your value>] or @YES/@NO.

    NSDate Since timezones are not supported, this will typically represent UTC dates.

    Using any unsupported type as a value (NSNull, NSObject, NSArray, NSDictionary for example) will NOT work. Be careful, as it may cause [editor save] to fail.

    NSURL Must not be longer than 2048 characters and must follow the format scheme://[authority][path][?query][#fragment].

    NSArray<NSString> Must not be longer than 25 items, only values of type NSString and must respect the string attribute limitations.

    Setting an attribute

    The custom attribute API works using an editor. You need to get an instance of the editor, enqueue your changes and then call save. Changes will NOT be sent until you call this method, please make sure you call it!

    There is one setAttribute/set(attribute:forKey:) method per attribute type. Use Xcode's autocompletion or see BatchProfileEditor.h for all available variants. If you're using Swift, the appropriate method will automatically be used according to the value's type.

    Those methods throw an error if key/value failed validation according to the rules expressed higher up in this documentation. Note that no error does not mean the value has already been sent to the server.

    You might be tempted to write helpers or loops that open and save many transactions in a row, with each transaction only doing one operation. Doing so prevents Batch from optimizing disk usage and network roundtrips, which impact your user's data plan and battery life. Please try to batch as many operations as you can in a single transaction.

    Removing attributes

    Use removeAttributeForKey to remove an attribute of any type.

    Reading attributes and tag collections

    Since Batch SDK v2, updating the user's data also update the profile's data to be accessible from your project scope. This mean the following APIs only read local data related to your installation and NOT to your profile.

    You may also have noticed that APIs to set Tags or Tag Collections have been removed and replaced by array attributes. These methods are backward-compatible and array attributes are converted into tag collections to not break your implementation.

    Reading attributes

    Reading tag collections

    Note: Since tags are limited in size and are case sensitive, reading them back might produce different results than what had been saved.

    Clearing installation data

    Clearing the installation data will delete all tags and attributes set on an installation and their local cache returned by fetchAttributes and fetchTagCollections. It can be done as following:

    Note: This will NOT affect the profile related data.

    Rich notifications setup

    Rich notification example

    iOS 10 introduced support for rich notifications: they can now contain custom content, such as images, videos, sounds or even a fully custom view controller. Batch comes with built-in support for these, but due to the way they're implemented, integration of a supplementary SDK is required. Don't worry, we've made it really easy.

    Note: This tutorial assumes that you haven't already added a Notification Content extension. If you do, jump straight to Integrating the Batch Extension SDK.

    Adding a Notification Service Extension

    In order to set up the Batch Extension SDK, you'll need a notification service extension. It's a standard iOS component that will take care of downloading rich content and add it to the notification.

    Open your Xcode project, click on the File menu and then pick New -> Target. Then, pick Notification Service Extension and fill in what's left of the wizard. You can name the extension as you wish: we will name it RichNotificationsExtension for the rest of this tutorial, and write it in Swift.

    Xcode will then ask you if you want to activate the scheme. Press Activate.

    Before going any further, you might want to check the extension's Deployment Traget. It usually is the latest iOS minor, meaning that your extension will not run on older iOS versions. We recommend that you set it to the lowest version of iOS that your app supports, but not lower than iOS 10.0 as this is the version that introduced this extension kind.

    Integrating Batch Extension SDK

    Framework integration

    BatchExtension is distributed as an .

    Add it using Xcode with its repository URL:

    Note: If you're getting the error: "Cannot subclass a class that was declared with the 'objc_subclassing_restricted' attribute", this is because Swift classes cannot be subclassed in Objective-C, and the extension code is pure Swift and SPM doesn't support mixed language packages. To fix this please use the if you wish to use SPM, use CocoaPods/Carthage or rewrite your extension code in Swift.

    Make sure you're setting the dependency on the extension target. If your Podfile existed before you created your extension, you might need to add it.

    Automatic integration

    If you don't have your own code, you've probably noticed that Xcode added some sample code for you:

    In order to have Batch automatically adding rich content to your notifications, simply remplace this code with:

    That's it, no code to write! Start your app, and try sending a rich push from the dashboard.

    Manual integration

    If you've already added your own extension code, you might want to manually integrate Batch and perform your own modifications to the notification content.

    First, import the extension SDK:

    Then, instanciate a BAERichNotificationHelper/RichNotificationHelper instance, and keep it as an instance variable of your UNNotificationServiceExtension instance.

    Note: You must NOT instanciate a new BAERichNotificationHelper/RichNotificationHelper object every time. The class needs to keep an internal state, and might not behave properly if it cannot.

    You can then use the following methods of BAERichNotificationHelper/RichNotificationHelper:

    • didReceive, which has the same signature as the one you're already in, but allows you to tweak the UNNotificationRequest beforehand

    • appendRichData, which will download and add attachments to the content, and call you back once done.

    Here's an example of a class that uses appendRichData:

    Configuring low data mode

    Starting with version 3.0.0, BatchExtension doesn't download rich notification content in low data mode anymore.

    To change this, use BAERichNotificationHelper/RichNotificationHelper:

    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:

    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

    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

    batch.user.event

    Send an event with associated tags and data.

    Arguments (required)

    Name
    Value

    batch.group

    Execute a list of up to 10 nested actions successively.

    Arguments (required)

    Name
    Value

    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

    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


    Events

    Batch allows you to track events that happen in your application. They automatically keep track of their count, the last time it happened and their value.

    Important Newly tracked events are hidden by default. You will need to manually display them from the dashboard settings.

    Tracking events

    Events are easy to use, but have some rules:

    AppDelegate.swift
    import Batch
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Start Batch.
        BatchSDK.start(withAPIKey: "YOUR_API_KEY")
        
        // 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();
        
        // Sets Batch as your UNUserNotificationCenterDelegate.
        // This will disable the legacy callbacks on your app delegate (didReceiveRemoteNotification, ...).
        // If you rely on those, do not add this line and please consider migrating to the UserNotification framework.
        //
        // If you already have a UNUserNotificationCenterDelegate implementation, do not add this line.
        // Instead, add Batch's callbacks in your implementation. See 'Advanced > Intercepting notifications', 
        BatchUNUserNotificationCenterDelegate.registerAsDelegate()
    
        return true
    }
    AppDelegate.m
    @import Batch;
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
     {  
        // Start Batch.
        [BatchSDK startWithAPIKey:@"YOUR_API_KEY"]; 
     	
        // 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];
    
        // Sets Batch as your UNUserNotificationCenterDelegate.
        // This will disable the legacy callbacks on your app delegate (didReceiveRemoteNotification, ...).
        // If you rely on those, do not add this line and please consider migrating to the UserNotification framework.
        //
        // If you already have a UNUserNotificationCenterDelegate implementation, do not add this line.
        // Instead, add Batch's callbacks in your implementation. See 'Advanced > Intercepting notifications', 
        [BatchUNUserNotificationCenterDelegate registerAsDelegate];
    
        return YES;
    }
        // Ask for the permission to display notifications
        // The push token will automatically be fetched by the SDK
        BatchPush.requestNotificationAuthorization()
        
        // Ask for the permission to display notifications with a completion handler.
        // The push token will automatically be fetched by the SDK
        BatchPush.requestNotificationAuthorization { success, error in
          // Handle permission result
        }
    
        // Ask for the provisionnal permission to display notifications
        // Notifications will NOT be displayed on the lock screen, or as a banner when the phone is unlocked.
        // They will directly be sent to the notification center, accessible when the user swipes up on the lockscreen, or down
        // The push token will automatically be fetched by the SDK.
        BatchPush.requestProvisionalNotificationAuthorization()
        // Ask for the permission to display notifications
        // The push token will automatically be fetched by the SDK
        [BatchPush requestNotificationAuthorization];
        
        // Ask for the permission to display notifications with a completion handler.
        // The push token will automatically be fetched by the SDK
        [BatchPush requestNotificationAuthorizationWithCompletionHandler:^(BOOL granted, NSError * _Nullable error) {
            // Handle permission result
        }];    
        // Ask for the provisionnal permission to display notifications
        // Notifications will NOT be displayed on the lock screen, or as a banner when the phone is unlocked.
        // They will directly be sent to the notification center, accessible when the user swipes up on the lockscreen, or down
        // The push token will automatically be fetched by the SDK.
        [BatchPush requestProvisionalNotificationAuthorization];
    }
    BatchUser.installationID()
    [BatchUser installationID];
    [Batch] - Push token (Apple Push Production): <push token>
    // 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;
    {"aps":{"badge":2}}
    {"aps":{"category":"CATEGORY_NAME"}}
    {"aps":{"sound":"mysound.caf"}}
    {"aps":{"content-available":1}}
     [BatchPush setSupportsAppNotificationSettings:true]
    event dispatcher
    Declare your Universal Links
    Then, link the framework to the extension just like you would with any Carthage dependency.
    open-source Swift package
    Pure Objective-C version
    Xcode target wizard screenshot
    Xcode deployment target
    Default extension code

    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}

    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"}

    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}}

    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":"..."}]]}

    t

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

    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:[BatchUserAction userActionWithIdentifier:@"<YOUR_ACTION_NAME>"
                                                               actionBlock:alertActionBlock]];
    Package.swift
    https://github.com/BatchLabs/Batch-iOS-SDK-Extension.git
    Podfile
    target 'RichNotifications' do
      use_frameworks!
      pod 'BatchExtension'
    end
    
    target 'App' do
      use_frameworks!
      pod 'Batch'
    end
    Cartfile
    github "BatchLabs/Batch-iOS-SDK-Extension"
    import BatchExtension
    
    class NotificationService: BAENotificationServiceExtension {
        
    }
    // NotificationService.h -- 
    @import BatchExtension;
    
    @interface NotificationService : BAENotificationServiceExtension
    @end
    
    // NotificationService.m
    #import "NotificationService.h"
    @implementation NotificationService
    @end
    
    import BatchExtension
    @import BatchExtension;
    
    or
    
    #import <BatchExtension/BatchExtension.h>
    class NotificationService: UNNotificationServiceExtension {
    
        let batchHelper = RichNotificationHelper()
        
        var bestAttemptContent: UNMutableNotificationContent?
    
        override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
            
            if let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent {
                // Modify the notification content here...
                bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
                
                // Ask Batch to download and add any rich content
                batchHelper.appendRichData(toContent: bestAttemptContent, completionHandler: { (content: UNNotificationContent?, err: Error?) in
                    if let err = err {
                        print("Error while appending rich notification attachments \(err)")
                    }
                    contentHandler(content ?? bestAttemptContent)
                })
            } else {
                contentHandler(request.content)
            }
        }
    }
    // NotificationService.h
    
    #import <UserNotifications/UserNotifications.h>
    
    @interface NotificationService : UNNotificationServiceExtension
    
    @end
    
    // NotificationService.m
    #import "NotificationService.h"
    @import BatchExtension;
    
    @interface NotificationService () {
        BAERichNotificationHelper *batchHelper;
    }
    @end
    
    @implementation NotificationService
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            batchHelper = [BAERichNotificationHelper new];
        }
        return self;
    }
    
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
        UNMutableNotificationContent *bestAttemptContent = [request.content mutableCopy];
        
        if (bestAttemptContent) {
            // Modify the notification content here...
            bestAttemptContent.title = [bestAttemptContent.title stringByAppendingString:@" [modified]"];
            
            // Ask Batch to download and add any rich content
            [batchHelper appendRichDataToContent:bestAttemptContent completionHandler:^(UNNotificationContent * _Nullable result, NSError * _Nullable error) {
                if (error) {
                    NSLog(@"Error while appending rich notification attachments %@", error);
                }
                
                if (result) {
                    contentHandler(result);
                } else {
                    contentHandler(bestAttemptContent);
                }
            }];
        } else {
            contentHandler(bestAttemptContent);
        }
    }
    
    @end
    
    import UserNotifications
    import BatchExtension
    
    class NotificationService: UNNotificationServiceExtension {
    
        override init() {
            super.init()
            RichNotificationHelper.allowInLowDataMode = true
        }
        
    }
    // NotificationService.m
    #import "NotificationService.h"
    
    @implementation NotificationService
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            BAERichNotificationHelper.allowInLowDataMode = true;
        }
        return self;
    }
    
    @end
    
    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?"}];
    Event names are strings. They should be made of letters, numbers or underscores ([a-z0-9_]) and can't be longer than 30 characters.
  • A custom data object can be attached. See the section "Event Attributes", right under this one.

  • Custom attributes have some reserved keys. See the section "Reserved event attributes" under "Event Attributes" for more info.

  • Here are some examples:

    // Simple event
    BatchProfile.trackEvent(name: "ad_seen")
    
    // Event with custom attributes
    BatchProfile.trackEvent(name: "add_to_cart", attributes: BatchEventAttributes { data in
      // Custom attribute
      data.put("bags"
    
    // Simple event
    [BatchProfile trackEvent:@"ad_seen"];
    
    // Event with custom attributes
    BatchEventAttributes *attributes = [BatchEventAttributes new];
    [attributes putString:@"sub_category" forKey:@"bags"]; // Custom attributes
    [attributes putString:
    

    Please test your implementation using our debug tool and profile view before releasing your app on the store.

    Event attributes

    Custom attributes can be attached to events using BatchEventAttributes. You will then use them when calling BatchProfile.trackEvent().

    Attribute name

    They should be made of letters, numbers or underscores ([a-z0-9_]) and can't be longer than 30 characters (e.g. has_premium). They will be automatically lowercased, so trying to use the same key with different casing will overwrite the previously set value.

    Attribute value

    Values must be any of the following types:

    • NSString, must not be longer than 300 characters and can't be empty. For better results, you should make them upper/lowercase and trim the whitespaces.

    • Bool

    • Floats/Doubles

    • NSInteger

    • NSDate

    • NSURL, not longer than 2048 characters and must follow the format scheme://[authority][path][?query][#fragment].

    • NSArray<NSString | BatchEventAttributes>, You can set array of strings (max 300chars) and array of objects. Max 25 items. You cannot mix several attribute types in one array. Arrays can't be nested.

    • Object (using BatchEventAttributes, Objects cannot have more than 3 levels of nesting.

    Setting a value for an existing key will overwrite it. Any attempt to add an invalid attribute will fail and the event will NOT be tracked. You can use the validateWithError method which return a list of human-readable errors to ensure your event is valid before sending it.

    Reserved event attributes

    Some event attributes have reserved keys, and are all prefixed by a $ sign. This is the list of currently reserved event attributes. You cannot set an event attribute starting by a $ sign.

    Key
    Description

    $label

    String - Optional

    Event label. Must be a string, will automatically be bridged as label for application event compatibility. Max 200 chars.

    $tags

    NSArray - Optional

    Event tags. Must be an array of string, will automatically be bridged as tags for application event compatibility. Max 10 items of type string, each no longer than 64chars. The SDK will automatically lowercase them, so two same strings with different casing do not count as two different tags

    In Batch SDK v1 you were able to set a label and tags at the root of an event, with the limit of 1 label and 10 tags.

    Batch SDK v2 introduced Object and Array types in event attributes. You can set more than one array on profiles events. This is only supported for profiles, and not on the install-centric data model, which currently powers push notification and In-App messages.

    However, it's still possible to set a label and tags on events in the install-centric data model by using $label and $tags, and activate compatibility flows. This way, you will be able to use this data for your push and in-app communications.

    If you are not running any orchestrations on the Mobile Engagement Platform (MEP), you should use String and Array attributes instead of legacy $label and $tags.

    Example

    Tracking user location

    You can now natively track a user location. This uses CoreLocation's standard CLLocation object, which you usually get from the CoreLocation itself. You can also instantiate one manually from a latitude/longitude.

    Here's an example:

    This data will allow you to send geo-targeted push notifications from the dashboard or the Campaigns API.

    The SDK will throttle location tracking to optimize network and battery usage. You can track one location event every 30 seconds, any attempt at updating the location sooner will be ignored by the SDK.

    Background events

    Events can be sent while the application is in the background by asking UIApplication to begin a background task. Once the event has been sent to the server, Batch will emit a BatchEventTrackerFinishedNotification NSNotification.

    Please note that this notification might be sent multiple times: you may want to dynamically add the observer and remove it once your event has been tracked.

    Here is a sample implementation:

    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.

    → Download the sample app on GitHub

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

    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:

    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.

    2

    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…"

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

    2

    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"

    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 , 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

    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.

    let attributes = BatchEventAttributes { data in
        data.put("man_clothes", forKey: "sub_category")
        data.put(Date(timeIntervalSince1970: 1713432899086), forKey: "end_of_sale_date")
        data.put([
            BatchEventAttributes { itemData in
                itemData.put("Basic Tee", forKey: "name")
                itemData.put("M", forKey: "size")
                itemData.put(23.99, forKey: "price")
                itemData.put(URL(string: "https://batch-store.com/basic-tee")!, forKey: "item_url")
                itemData.put(URL(string: "https://batch-store.com/basic-tee/black/image.png")!, forKey: "item_image")
                itemData.put(true, forKey: "in_sales")
            },
            BatchEventAttributes { itemData in
                itemData.put("Short socks pack x3", forKey: "name")
                itemData.put("38-40", forKey: "size")
                itemData.put(15.99, forKey: "price")
                itemData.put(URL(string: "https://batch-store.com/short-socks-pack-x3")!, forKey: "item_url")
                itemData.put(URL(string: "https://batch-store.com/short-socks-pack-x3/image.png")!, forKey: "item_image")
                itemData.put(false, forKey: "in_sales")
            }
        ], forKey: "items_list")
        data.put("accessories", forKey: "$label")
        data.put(["first_purchase", "in_promo"], forKey: "$tags")
    }
    do {
        let _ = try attributes.validate()
        BatchProfile.trackEvent(name: "purchased", attributes: attributes)
    } catch let error {
        print("Event validation error: \(error.localizedDescription)")
    }
    BatchEventAttributes *attributes = [BatchEventAttributes new];
    [attributes putString:@"man_clothes" forKey:@"sub_category"];
    [attributes putDate:[NSDate dateWithTimeIntervalSince1970:1713432899086] forKey:@"end_of_sale_date"];
    
    NSMutableArray *itemsList = [NSMutableArray array];
    BatchEventAttributes *item1 = [BatchEventAttributes new];
    [item1 putString:@"Basic Tee" forKey:@"name"];
    [item1 putString:@"M" forKey:@"size"];
    [item1 putFloat:23.99 forKey:@"price"];
    [item1 putURL:[NSURL URLWithString:@"https://batch-store.com/basic-tee"] forKey:@"item_url"];
    [item1 putURL:[NSURL URLWithString:@"https://batch-store.com/basic-tee/black/image.png"] forKey:@"item_image"];
    [item1 putBool:YES forKey:@"in_sales"];
    [itemsList addObject:item1];
    
    BatchEventAttributes *item2 = [BatchEventAttributes new];
    [item2 putString:@"Short socks pack x3" forKey:@"name"];
    [item2 putString:@"38-40" forKey:@"size"];
    [item2 putFloat:15.99 forKey:@"price"];
    [item2 putURL:[NSURL URLWithString:@"https://batch-store.com/short-socks-pack-x3"] forKey:@"item_url"];
    [item2 putURL:[NSURL URLWithString:@"https://batch-store.com/short-socks-pack-x3/image.png"] forKey:@"item_image"];
    [item2 putBool:NO forKey:@"in_sales"];
    [itemsList addObject:item2];
    
    [attributes putObjectArray:itemsList forKey:@"items_list"];
    [attributes putString:@"accessories" forKey:@"$label"];
    [attributes putStringArray:@[@"first_purchase", @"in_promo"] forKey:@"$tags"];
    
    NSError *err;
    [attributes validateWithError:&err];
    if (err == nil) {
        [BatchProfile trackEventWithName:@"purchased" attributes:attributes];
    } else {
        NSLog(@"Event validation error: %@", err.description);
    }
    // let location: CLLocation = [...]
    BatchProfile.trackLocation(location)
    // CLLocation *location = [...];
    [BatchProfile trackLocation:location];
    @objc
    class BackgroundEventSender : NSObject {
        var eventBackgroundEventTaskID: UIBackgroundTaskIdentifier?
        
        override init() {
            super.init()
            NotificationCenter.default.addObserver(self, selector: #selector(finishBackgroundTask), name: Notification.Name.BatchEventTrackerFinished, object: nil)
        }
        
        func trackBackgroundEvent() {
            guard eventBackgroundEventTaskID == nil else {
                // This sample code doesn't support tracking multiple events at once
                return
            }
            
            eventBackgroundEventTaskID = UIApplication.shared.beginBackgroundTask(withName: "batch_background_event", expirationHandler: self.finishBackgroundTask)
            
            BatchProfile.trackEvent(name: "SAMPLE_BACKGROUND_EVENT")
    
        }
        
        @objc
        func finishBackgroundTask() {
            guard let task = eventBackgroundEventTaskID else {
                return
            }
            self.eventBackgroundEventTaskID = nil
            UIApplication.shared.endBackgroundTask(task)
        }
    }
    // Get an editor instance.
    // You need to save this in a local variable until you call save
    // Editor instances don't share changes, and calling save on an empty editor will do nothing
    let editor = BatchProfile.editor()
    
    // Set an attribute. try? allows a potential error to be silently ignored
    // This example is a valid key/attribute pair, and will not throw an error.
    try? editor.set(attribute: 26, forKey:"age")
    
    // Set an array attribute
    try? editor.set(attribute: ["add_to_cart", "has_bought"], forKey: "actions")
    
    // Add a string to an array attribute 
    try? editor.addToStringArray(item: "has_bought", forKey: "actions")
    
    
    // Set an attribute with error handling.
    do {
        // Invalid attribute name, $ is a forbidden character
        try editor.set(attribute: "patricia", forKey: "fir$t_name")
    } catch {
        // Handle the error here.
        // Error is of type BatchProfileError if you want to specifically
        // handle it.
    }
    
    // Don't forget to save the changes
    editor.save() 
    
    // Alternatively you can use `editWithBlock` and make all your changes in a closure.
    BatchProfile.editor { editor in
        try? editor.set(attribute: "patricia", forKey: "firt_name")
         // No need to save here since Batch will automatically save your change after executing the block.
    }
    // Get an editor instance.
    // You need to save this in a local variable until you call save
    // Editor instances don't share changes, and calling save on an empty editor will do nothing
    BatchProfileEditor *editor = [BatchProfile editor];
    
    // Set an attribute, silently ignoring a potential error
    // This example is a valid key/attribute pair, and will not throw an error.
    [editor setIntegerAttribute:@26 forKey:@"age" error:nil];
    
    // Set an array attribute
    [editor setStringArrayAttribute: @[@"added_to_cart", @"has_bought"] forKey:@"actions" error:nil];
    
    // Add a string to an array attribute 
    [editor addItemToStringArrayAttribute:@"actions" forKey:@"has_bought" error:nil]
    
    // Set an attribute with error handling.
    NSError *err;
    if (![editor setStringAttribute:@"patricia" forKey:@"fir$t_name" error:&err]) { ; // Invalid attribute name, $ is a forbidden character
        // Handle the error here.
    }
    // Don't forget to save the changes
    [editor save]; 
    
    // Alternatively you can use `editWithBlock` and make all your changes in a closure.
    [BatchProfile editWithBlock:^(BatchProfileEditor * _Nonnull editor) {
        [editor setStringAttribute:@"patricia" forKey:@"firt_name" error:nil];
        // No need to save here since Batch will automatically save your change after executing the block.
    }];
    // Get an editor instance.
    // You need to save this in a local variable until you call save
    // Editor instances don't share changes, and calling save on an empty editor will do nothing
    let editor = BatchProfile.editor()
    
    // Remove an attribute
    editor.removeAttribute(forKey: "age") 
    
    // Remove a string from an array
    try? editor.removeFromStringArray(item: "has_bought", forKey: "actions")
    
    // Don't forget to save the changes
    editor.save() 
    // Get an editor instance.
    // You need to save this in a local variable until you call save
    // Editor instances don't share changes, and calling save on an empty editor will do nothing
    BatchProfileEditor *editor = [BatchProfile editor];
    
    // Remove an attribute
    [editor removeAttributeForKey:@"age"]; 
    
    // Remove a string from an array
    [editor removeItemFromStringArrayAttribute:@"has_bought" forKey:@"actions" error:nil];
    
    // Don't forget to save the changes
    [editor save];
    BatchUser.fetchAttributes { attributes in
        // Attributes are retrieved in the form of a dictionary
        // Values are encapsulated in an instance of BatchUserAttribute
        let attribute: BatchUserAttribute = attributes["age"]
                
        // BatchUserAttribute holds a reference to the value of the attribute
        let rawValue: Any = attribute.value // Raw value is not typed
        print(rawValue) // Prints "NSNumber(26)"
                
        // The type of the value is specified via a BatchUserAttributeType enumeration
        print(attribute.type) // Prints "BatchUserAttributeTypeLongLong"
                
        // To obtain a typed result you can use one of the four helper methods
        attribute.numberValue() // Will return "26" here
        attribute.dateValue()   // Will return nil here
        attribute.stringValue() // Will return nil here
        attribute.urlValue()    // Will return nil here
    }
    [BatchUser fetchAttributes:^(NSDictionary<NSString *,BatchUserAttribute *> * _Nullable attributes) {
        // Attributes are retrieved in the form of a dictionary
        // Values are encapsulated in an instance of BatchUserAttribute
        BatchUserAttribute *attribute = attributes[@"age"];
    
        // BatchUserAttribute holds a reference to the value of the attribute
        id rawValue = attribute.value; // Raw value is not typed
        NSLog(rawValue); // Prints "NSNumber(26)"
            
        // The type of the value is specified via a BatchUserAttributeType enumeration
        NSLog(attribute.type); // Prints "BatchUserAttributeTypeLongLong"
        
        // To obtain a typed result you can use one of the three helper methods
        [attribute numberValue];    // Will return "26" here
        [attribute dateValue];      // Will return nil here
        [attribute stringValue];    // Will return nil here
    }];
    BatchUser.fetchTags { tagCollections in
        // Tags are also retrieved in the form of a dictionary
        // Keys are names of collections, values are sets of tags
        let tagCollection: Set<String> = tagCollections["actions"]
        print(tagCollection) // Prints "["has_bought"]"
    }
    [BatchUser fetchTags:^(NSDictionary<NSString *,NSSet<NSString *> *> * _Nullable tagCollections) {
        // Tags are also retrieved in the form of a dictionary
        // Keys are names of collections, values are sets of tags
        NSSet<NSString *> *tagCollection = tagCollections[@"actions"];
        NSLog(tagCollection) // Prints "["has_bought"]"
    }];
    BatchUser.clearInstallationData();
    [BatchUser clearInstallationData];
    , forKey
    :
    "sub_category"
    )
    // Compatibility reserved key
    data.put("activity", forKey: "$label")
    })
    @"$label"
    forKey
    :
    @"activity"
    ];
    // Compatibility reserved key
    [BatchProfile trackEventWithName:@"add_to_cart" attributes:attributes];
    Step 2

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

    Link with libraries build phase
    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.

    Linker flags

    You can now proceed with the integration of the SDK

    Step 2.

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

    Entitlements

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

    SSL certificate popup

    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.

    CSR Upload

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

    Generated Certificate
    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.

    Keychain export menu

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

    Keychain export password prompt

    If the Personal Information Exchange (.p12) option is grayed out in the export sheet, make sure "My Certificates" is selected in Keychain Access.

    3

    Step 3.

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

    BatchSettings

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

    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"

    Then download the generated provisioning profile from the next screen by selecting the "Download" button.

    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.

    Apple Developer website
    Project Navigator view
    Xcode add framework to project
    Keychain
    Certificate

    Troubleshooting

    If you are having trouble integrating the SDK, uploading your certificates or sending notifications, here are some suggestions.

    Displaying a stable debug ID

    The Installation ID is an ID generated by Batch for all the installs the first time your users open the app. You can safely display it in your app to simplify the debug process.

    You can use that ID in the debug tool (Dashboard settings > Debug) to find your own install, see the data Batch has on it and send test notifications to your device.

    Your end users can also send it to you so you can understand why they are not receiving push notifications or not seeing an In-App message. This is useful when your app doesn't share any advertising ID with Batch.

    You can also store the install ID on your end and reuse it later to target specific installs using the .

    Retrieving the installation ID

    You can retrieve the installation ID by calling the following methods:

    Displaying the installation ID

    That installation ID can be exposed to your end users. You can insert it in a diagnostic email automatically generated when users report an issue from the app or hide it in an easter egg.

    Copying the installation ID

    Batch will copy the installation ID to the clipboard when the application appears in the foreground 4 times within 20 seconds. This is enabled by default, you can disable it at any time by using:

    Enabling internal logs

    Internal logs can be useful to you or our support team to debug issues that might not be debuggable using public logs. They're disabled by default as they are very verbose by design.

    They can be enabled in two ways:

    Using code

    Please make sure that you do not enable internal logs on an App Store build.

    Using a command line argument

    Command line arguments are only usable when an app is launched by Xcode or while running tests. This is an easy way to make sure that logs never end up in a production build. However, they cannot be allow for internal logs to be read by testers.

    Batch will enable internal logs if the app has been launched with -BatchSDKEnableInternalLogs:

    Implementing Batch debugger

    The debug view is a UI with multiple debug features, allowing you to see the native and custom data attached to your install. You can also see the list of In-App campaigns already stored in your device.

    This is useful for development purposes and for internal uses only, especially if your team cannot use an advertising or a custom user id to find their device in the debug tool and send test notifications to their device.

    Identifiers

    Shows a list of native information you can use for basic debug:

    • Batch SDK version

    • Installation ID

    • Advertising ID

    • Push token

    You can easily share that report by clicking the share button in the top right corner.

    Custom user data

    Shows the custom user data attached to your install, excluding events. This is useful to:

    • Check the Custom User ID attached to the install ().

    • See the list of attributes / tag collections attached to your install. You can use that part to see if the app has been tagged correctly or understand why your device didn't receive a notification based on a one of these information.

    In-App campaigns

    Use that part to manually pull the campaigns available on Batch servers for your install and to see the list of stored In-App campaigns.

    Implementing the debugger

    The debugger should be presented modally:

    You can hide it in an easter egg (e.g. display it after 10 taps on specific button, etc) or simply display it in your app settings for your development versions exclusively.

    Common issues

    I’m not seeing any data on my dashboard

    There are several points you should check if Batch doesn't show any data on your dashboard:

    • 24h delay. Installs, DAU, starts and redeems shown on the Analytics tab are updated on a 24h basis.

    • Integration issue. If you still don't see any stats for your app 24h after changing the API key, see if you find anything related to Batch in your logs and double check your integration using the documentation.

    "Unable to find a token"

    There are several points you should check if Batch doesn't detect any device tokens:

    • Push disabled: See if you have enabled the notifications for your app in the iOS settings (Settings → Notifications).

    • Look for errors: Check your Xcode logs to see if Batch published any error messages.

    • Provisioning issue: Make sure you have correctly set up your provisioning profile with Xcode.

    My device doesn't seem to receive any notifications

    If you are not receiving any of the notifications sent from Batch, here are some suggestions to find the issue:

    • App opened: Make sure your app is not opened in the foreground during your tests. By default iOS only displays notifications for apps opened in the background: It only calls your delegate, which can be invisible to you if you don't log anything.

    • Push disabled: See if you have enabled the notifications for your app in iOS settings (Settings → Notifications).

    • Connection issues: Connect your device to a 3G/4G network or try to disconnect/reconnect to your WiFi network.

    I'm receiving notifications twice

    You may receive the same notification twice if you have reinstalled your app with different API keys, several times on the same device (Dev/Live). Rest assured, your users won't receive the same notification twice.

    Certificates issues

    The .p12 export button is greyed out

    Make sure you have selected the default login keychain in the left pane of the Keychain application, and selected "My certificates" in the top filtering bar of the Keychain application.

    "Wrong file or password"

    First, double-check your certificate password. If your password is correct, make sure your certificate is not named "Apple Production IOS Push Services" or "Apple Development IOS Push Services". These are not supported anymore. You will need to if it's the case.

    "Unable to parse the p12"

    You cannot upload Apple's certificate directly. You will find more information on certificate generation .

    “Legacy or development certificates are no longer supported”

    Development certificates or production certificates named "Apple Production IOS Push Services" are not supported anymore. Please generate a new .

    Test push issues

    "DeviceTokenNotForTopic"

    DeviceTokenNotForTopic errors happen when your app's bundle ID and certificate's bundle ID don't match.

    Please ensure you set up your bundle ID correctly in your Xcode project settings.

    "BadDeviceToken"

    BadDeviceToken errors can happen for two reasons:

    • Invalid token: On iOS 9 and higher, your device token may change if you uninstall/reinstall an app. Please make sure your token hasn't changed since your last tests. See how to obtain your token .

    • Wrong environment: If your device token is valid, make sure you have chosen the right environment (sandbox/production) from Batch settings (Settings → Push settings). You can find the right environment for your token with our .

    "The attempt to send a [test] notification was rejected : invalid certificate"

    If you see an "Invalid certificate" error, this means that:

    • Legacy certificates: You are currently using a legacy certificate and the Development or the Production certificates are missing.

    • Delay: Batch is still processing your certificates. Please try again in a few minutes and reach us if the problem persists.

    "There was an unknown error while processing the request"

    If you are getting an unknown error, please contact us at or by clicking on the question mark in the bottom right corner. Our team will try to find more details on the issue.

    APNS/internal errors

    You can find the list of APNS and Batch internal errors in the , by hovering on the error count.

    apns_invalid_token

    APNS can send this feedback when:

    • Invalid token: The token Batch tries to push is not valid anymore. This usually happens when users uninstall/reinstall (iOS 9.x and higer) your app or disable notifications.

    • Wrong environment: Make sure you are targeting the right environment (sandbox/production) for your token.

    apns2_device_token_not_for_topic

    Happens when your app's bundle ID and certificate's bundle ID don't match. Make sure you set up your bundle ID correctly in your Xcode project settings.

    apns2_topic_disallowed

    Make sure the App ID/Bundle ID or the team ID you used for your P8 certificate are correct.

    max_retry_attempts

    This internal error happens when the number of maximum send attempts is reached for a token. Feel free to reach us if you are seeing to many max_retry_attemps errors.

    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 Apple's official documentations of notification actions.

    Push example with actions and image

    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 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)

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

    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 . 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

    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 .

    Link with Custom Actions

    A great way to clean up your code is to use 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 and any future feature that will support calling actions you've defined remotely.

    Once you've created your , 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

    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:

    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 .

    Mobile landings

    Mobile Landings allow you to easily introduce continuity between your app, and your pushes: A user opening a push will be greeted by a rich message related to what they opened, rather than just ending up on your app's main menu.

    They're included in the Startup, Business and Enterprise plans.

    Displaying the message

    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 beforehand.

    Upgrading the SDK version


    Please test your implementation using our and before releasing your app on the store. Make sure you're unwrapping your optionals!

    debug tool
    profile view
    APN Environment
    Test notifications: Try to send a test notification to your device from the message editor ("Push" tab → "New push campaign" → "Send a test").
    Transactional API
    see more here
    generate a new production certificate
    here
    .p8 certificate
    here
    debug tool
    support@batch.com
    Notifications tab
    Debug iOS
    Example
    Adding the command line flag using Xcode's scheme editor
    Debugger overview
    Debugger native data
    Debugger custom data
    Debugger in-app
    Bundle ID

    Touch ID/Passcode requirement

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

  • ...

  • iOS8/9
    iOS10
    here in our Batchcasts sample project
    userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
    application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:
    Custom Actions
    Mobile Landings
    Custom Actions
    BatchCasts iOS Sample
    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
    Automatic mode

    There's no code required to make mobile landings work in automatic mode: just attach a landing to your push campaign, and Batch will display it.

    You might want to go further into this documentation, and setup your delegate, or head to the Custom Actions documentation to add custom behaviour to buttons.

    Manual mode

    You may want to be in control of if, when and how landings will be loaded and displayed. Batch allows you to disable automatic displaying, and handle loading and displaying the view controller itself.

    First, you'll need to implement UNUserNotificationCenterDelegate in a class (for more information, please see the Intercepting notifications part):

    Then, you have to disable the automatic mode and set your class as your default UNUserNotificationCenter delegate:

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

    Controlling the display using "Do Not Disturb mode"

    "Do Not Disturb" (DnD) feature: It allows you to tell Batch to hold on a mobile landing for you, rather than display it without using the fully manual mode. For example, if launching your app results in a splash screen or a fullscreen ad, you might find it undesirable to have Batch display something on top of it.

    Turning on "Do Not Disturb" mode will make Batch enqueue the latest mobile landing, rather than display it.

    Toggling DnD

    Now, when you don't want Batch to automatically display, turn on Do Not Disturb:

    Once you want to start showing landings automatically, call the method with false to turn it off.

    Note: Disabling Do Not Disturb mode does NOT make Batch show the enqueued message

    Displaying pending mobile landings

    After coming back from DnD mode, you might want to show the enqueued message, as Batch will not do that automatically. Batch exposes two properties/methods for managing the queue:

    • BatchMessaging.hasPendingMessage , allowing you to peek into the queue.

    • BatchMessaging.popPendingMessage() , allowing you to fetch the pending message (if any). Since calling this makes Batch delete its reference to it to save memory, further calls might return nil.

    • BatchMessaging.showPendingMessage() , allowing you to try to show the pending message, if any.

    Here is a quick example of how they can be used:

    Note: Only the latest message is queued: if a mobile landing arrives while one is still pending, it will overwrite the previous one.

    Listening to lifecycle events and reacting to button actions

    Setting up a delegate

    Batch's messaging module supports setting up a delegate, which can be used for analytics:

    It can be any object that implements the BatchMessagingDelegate protocol.

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

    Like most delegates on iOS, Batch only stores a weak reference to it. Make sure you keep a reference to your object instance so it isn't released

    Analytics delegate

    Batch can notify your delegate of lifecycle events of the in-app messages:

    The messageIdentifier variable is the message tracking identifier you've configured in the dashboard. It can be nil if you didn't specify one.

    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:

    The size will be overriden later, so you can use anything you want. Make sure you provide both a normal and a bold font, even if they are the same.

    This assumes you've already got custom UIFonts working. If you don't, you can find a great tutorial here.

    Troubleshooting

    Nothing happens when I press an actionable button

    Take a look at your application logs in Xcode, the SDK might try to warn you about an issue. Here are some the common messages and their probable cause:

    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.

    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.

    Mobile landings visual example

    ⚠️ 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.

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

    Update your Podfile as following and run pod update.

    Podfile
    pod 'Batch', '~> 3.0'

    Update your Cartfile if necessary and run carthage update Batch.

    Cartfile
    github "BatchLabs/Batch-iOS-SDK" ~> 3.0

    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 manual mode section.

    Messaging Delegate

    The following methods in BatchMessagingDelegate have been removed:

    • batchMessageWasCancelledByAutoclose

    • batchMessageWasCancelledByUserAction

    • batchMessageWasCancelledByError

    • batchWebViewMessageDidTriggerAction

    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 listening lifecycle event section.


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

    migration guide from V1
    BatchUser.installationID()
    [BatchUser installationID];
    class AppDelegate: UIApplicationDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            // This can be called at any time, even before starting Batch.
            BatchSDK.setEnablesFindMyInstallation(false)
            // ...
        }
    }
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // This can be called at any time, even before starting Batch.
        [BatchSDK setEnablesFindMyInstallation:false]
        // ...
    }
    class AppDelegate: UIApplicationDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            // This can be called at any time, even before starting Batch.
            // Enabling logs as early as possible ensures that you don't miss important events.
            BatchSDK.setInternalLogsEnabled(true)
            // ...
        }
    }
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // This can be called at any time, even before starting Batch.
        // Enabling logs as early as possible ensures that you don't miss important events.
        [BatchSDK setInternalLogsEnabled:true];
        // ...
    }
    if let debugVC = BatchSDK.makeDebugViewController() {
        present(debugVC, animated: true, completion: nil)
    }
    import SwiftUI
    import Batch
    
    struct BatchDebugView: UIViewControllerRepresentable {
        func makeUIViewController(context: Context) -> some UIViewController {
            return BatchSDK.makeDebugViewController() ?? UIViewController()
        }
        
        func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        }
    }
    
    struct ContentView: View {
        @State var showBatchDebug = false
        
        var body: some View {
            VStack {
                Button("Open Batch Debug") {
                    self.showBatchDebug = true
                }.sheet(isPresented: self.$showBatchDebug) {
                    BatchDebugView()
                }
            }
        }
    }
    UIViewController *debugVC = [BatchSDK makeDebugViewController];
    if (debugVC) {
        [self presentViewController:debugVC animated:YES completion:nil];
    }
    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"}}
    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];
    @objc
    class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
        func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
            AppDelegate.tryShowBatchMessage(userInfo: response.notification.request.content.userInfo as [NSObject : AnyObject])
            BatchPush.handle(userNotificationCenter: center, didReceive: response)
            completionHandler()
        }
        
        func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
            AppDelegate.tryShowBatchMessage(userInfo: notification.request.content.userInfo as [NSObject : AnyObject])
            BatchPush.handle(userNotificationCenter: center, willPresent: notification, willShowSystemForegroundAlert: true)
            completionHandler([.sound, .alert, .badge])
        }
    }
    // 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 {
        [AppDelegate tryShowBatchMessage:response.notification.request.content.userInfo];
        [BatchPush handleUserNotificationCenter:center didReceiveNotificationResponse:response];
        completionHandler();
    }
    
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center
           willPresentNotification:(UNNotification *)notification
             withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
        [AppDelegate tryShowBatchMessage:response.notification.request.content.userInfo];
        [BatchPush handleUserNotificationCenter:center willPresentNotification:notification willShowSystemForegroundAlert:YES];
        completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound)
    }
    
    @end
    class AppDelegate: UIResponder, UIApplicationDelegate {
        let notificationDelegate = NotificationDelegate()
        
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            BatchMessaging.setAutomaticMode(on: false)
            UNUserNotificationCenter.current().delegate = notificationDelegate
        }
    }
    @interface AppDelegate ()
    {
        NotificationDelegate *notificationDelegate;
    }
    @end
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        [BatchMessaging setAutomaticMode:NO];
        if ([UNUserNotificationCenter class]) {
            notificationDelegate = [NotificationDelegate new];
            [[UNUserNotificationCenter currentNotificationCenter] setDelegate:notificationDelegate];
        }
    }
    
    @end
    AppDelegate.swift
    static func tryShowBatchMessage(userInfo: [NSObject : AnyObject]) {
        
        // Put the display code in this if block if you don't want the messaging to show when the user is already using the app
        // if UIApplication.shared.applicationState == .inactive {
        // }
        
        guard let message = BatchMessaging.message(fromPushPayload: userInfo) else {
            // No message from push payload
            return
        }
        
        guard let vc = try? BatchMessaging.loadViewController(for:message) else {
            print("An error occurred while loading Batch's messaging view")
            return
        }
        // Let Batch present it itself
        BatchMessaging.present(vc)
        
        
        // Or,
        // if you want to display the message yourself, you will need to check if the VC should be presented in
        // its own window, to allow user interaction around the message if needed. Banners require this, for example.
        guard let batchVC = vc as? BatchMessagingViewController else {
            print("The loaded VC does not satisfy the BatchMessagingViewController protocol like it should")
            return
        }
        
        if (batchVC.shouldDisplayInSeparateWindow) {
            // Display in your own UIWindow
        } else {
            var targetVC = UIApplication.shared.keyWindow?.rootViewController
            if let presentedVC = targetVC?.presentedViewController {
                targetVC = presentedVC
            }
            targetVC?.present(vc, animated: true, completion: nil)
        }
    }
    AppDelegate.m
    + (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) {
                // Let Batch present it itself
                [BatchMessaging presentMessagingViewController:vc];
                
                // Or,
                // if you want to display the message yourself, you will need to check if the VC should be presented in
                // its own window, to allow user interaction around the message if needed. Banners require this, for example.
                if ([vc conformsToProtocol:@protocol(BatchMessagingViewController)]) {
                    if (((id<BatchMessagingViewController>)vc).shouldDisplayInSeparateWindow) {
                        // Show this VC in your own UIWindow
                    } else {
                        UIViewController *targetVC = [[[UIApplication sharedApplication] keyWindow] rootViewController];
                        UIViewController *presentedVC = targetVC.presentedViewController;
                        if (presentedVC) {
                            targetVC = presentedVC;
                        }
                        [targetVC presentViewController:vc animated:YES completion:nil];
                    }
                }
                
            }
        }
    }
    
    BatchMessaging.doNotDisturb = true
    BatchMessaging.doNotDisturb = YES;
    func splashScreenDidDisappear() {
      BatchMessaging.doNotDisturb = false
      BatchMessaging.showPendingMessage()
    }
    - (void)splashScreenDidDisappear {
      BatchMessaging.doNotDisturb = NO;
      [BatchMessaging showPendingMessage];
    }
    
    var messagingDelegate: SampleBatchMessagingDelegate?
    
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
      [...]
      
      messagingDelegate = SampleBatchMessagingDelegate()
      BatchMessaging.delegate = messagingDelegate
      
      [...]
    }
    // 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];
        
        [...]
    }
    import Foundation
    import Batch.Messaging
    
    public class SampleBatchMessagingDelegate: NSObject, BatchMessagingDelegate {
        
        public func batchMessageDidAppear(messageIdentifier: String?) {
            print("SampleBatchMessagingDelegate - batchMessageDidAppear: \(messageIdentifier)")
        }
        
        public func batchMessageDidTriggerAction(_ action: BatchMessageAction, messageIdentifier identifier: String?, actionIndex index: Int) {
            // A button or global tap action has been triggered
    
            // If the action is a CTA (button), you can fetch the label
            if let cta = action as? BatchMessageCTA {
                let label = cta.label
                // [...]
            }
    
            // CTAs are allowed to simply be a "dismiss" action, in which case they
            // will not have any associated action name and arguments
            if !action.isDismissAction() {
                let actionName = action.action // not null
    
                // [...]
            }
        }
        
        public func batchMessageDidDisappear(_ messageIdentifier: String?, reason: BatchMessagingCloseReason) {
            print("SampleBatchMessagingDelegate - batchMessageDidDisappear: \(messageIdentifier) with reason: \(reason)"
        }
    }
    // 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
    // Set a custom font
    let font = UIFont(name: "MyFont", size: 10)
    let boldFont = UIFont(name: "MyFont-Bold", size: 10)
    BatchMessaging.setFontOverride(font, boldFont: boldFont)
    // or variant with italic and boldItalic
    // BatchMessaging.setFontOverride(UIFont?, boldFont: UIFont?, italicFont: UIFont?, boldItalicFont: UIFont?)
    
    // Clear the custom font, and use the system one
    BatchMessaging.setFontOverride(nil, boldFont: nil)
    // 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];
    An error occured while making a NSURL for the following link: '<your deeplink>', ignoring deeplink action.
    The action 'ACTION NAME' couldn't be found. Did you forget to register it?
    // 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
    
    // 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
    batchMessageDidTriggerAction:(BatchMessageAction *_Nonnull)action
                       messageIdentifier:(NSString *_Nullable)identifier
                           ctaIdentifier:(NSString *_Nonnull)ctaIdentifier;

    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

    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

    SDK changelog

    3.2.0


    Compiles with Xcode 26.1.1

    Batch requires Xcode 26.1.1 and iOS 15.0 or higher (iOS 16.0 or higher for Mac Catalyst only)

    Core

    • Reintroduced the arm64e architecture slice since the previous removal was a temporary measure due to App Store/TestFlight validation issues observed with arm64e slices compiled using early iOS 18 SDKs.

    Profile

    • Profile string attributes now support up to 300 characters for the Customer Engagement Platform (CEP). The limit for the Mobile Engagement Platform (MEP) remains 64 characters. Attributes set via BatchProfileEditor.setStringAttribute() longer than 64 characters will only be applied to the CEP.

    • Event string attributes now support up to 300 characters for the Customer Engagement Platform (CEP). The limit for the Mobile Engagement Platform (MEP) remains 200 characters. Attributes set via BatchEventAttributes.putString() longer than 200 characters will only be applied to the CEP.

    3.1.0


    Compiles with Xcode 16.4 Batch requires Xcode 16.4 and iOS 15.0 or higher

    Core

    • Fixed an issue where some GIF files would play too fast

    • Fixed an issue where didRegisterForRemoteNotificationsWithDeviceToken/didFailToRegisterForRemoteNotificationsWithError UIApplicationDelegate methods broke when swizzling was enabled on a SwiftUI lifecycle application.

    Messaging

    • Introduced In-App Automations within the Customer Engagement Platform.

    • Introduced Spacer component for creating flexible empty space within In-App messages.

    • Added support for In-App WebView format within the Customer Engagement Platform.

    • Added fill

    3.0.3


    Compiles with Xcode 16.3 Batch requires Xcode 16.3 and iOS 15.0 or higher

    This version is fully compatible with iOS 26 and Xcode 26

    Core

    • Re-release of 3.0.2 due to a build script issue.

    3.0.2


    Compiles with Xcode 16.3 Batch requires Xcode 16.3 and iOS 15.0 or higher

    Core

    • Important Xcode 26 warning: We have had reports of the SDK failing to pass App Store/Testflight validation due to the presence of an arm64e slice compiled with the iOS 18 SDK. Therefore, we removed the arm64e slice for the time being, planning to reintroduce it at a later date when we can compile with a stable iOS 26 SDK. Please contact our support team if this is a problem for you.

    Messaging

    • Fixed an issue where In-App messages might not display during NEW SESSION events.

    3.0.1


    Compiles with Xcode 16.3. Batch requires Xcode 16.3 and iOS 15.0 or higher

    Core

    • Re-release of 3.0.0 due to a misalignment of deployment targets across package managers

    3.0.0


    This is a major release, please see our for more info on how update your current Batch implementation.

    Compiles with Xcode 16.3. Batch requires Xcode 16.3 and iOS 15.0 or higher

    Core

    • Added NS_SWIFT_UI_ACTOR annotations on a couple of methods to prevent threading related bugs. While this is a breaking change, most implementations won't notice anything.

    Messaging

    • Added Mobile Landings with Push v2 which can be created from the CEP composer in the Batch Dashboard.

    • Added new BatchInAppDelegate protocol and inAppDelegate property on BatchMessaging, in order to handler In-App messages manually.

    • Removed individual close callback methods (batchMessageWasCancelledByAutoclose, batchMessageWasCancelledByUser

    2.1.2

    Compiles with Xcode 16.0 Batch requires Xcode 16.0 and iOS 13.0 or higher

    This version is fully compatible with iOS 26 and Xcode 26

    Core

    • Added support for Xcode 26.

    • Removed arm64e slice.

    • Fixed an issue where didRegisterForRemoteNotificationsWithDeviceToken/didFailToRegisterForRemoteNotificationsWithError UIApplicationDelegate methods broke when swizzling was enabled on a SwiftUI lifecycle application.

    2.1.1


    Compiles with Xcode 16.0 Batch requires Xcode 16.0 and iOS 13.0 or higher

    Core

    • Re-release of 2.1.0 with a valid codesignature.

    2.1.0


    Compiles with Xcode 16.0 Batch requires Xcode 16.0 and iOS 13.0 or higher

    Profile

    • Added setPhoneNumber API to the BatchProfileEditor class. This requires to have a user identifier registered or to call the identify method beforehand.

    • Added setSMSMarketingSubscriptionState API to the BatchProfileEditor class.

    2.0.2


    Compiles with Xcode 15.3 Batch requires Xcode 15.3 and iOS 13.0 or higher

    Core

    • Due to CocoaPod bugs, we removed Code Signing and Mergable Library support from Batch when installed via CocoaPods. If those are important to you, please use Swift Package Manager or manually integrate the framework.

    2.0.1


    Compiles with Xcode 15.3 Batch requires Xcode 15.3 and iOS 13.0 or higher

    Event

    • Fixed an issue where tracking an event without custom attributes wasn't sent.

    2.0.0


    This is a major release, please see our for more info on how update your current Batch implementation.

    Batch requires Xcode 15.3 and iOS 13.0 or higher

    Linking

    • Dropped support for iOS 12 and lower.

    • Batch is now distributed as a dynamic framework. You can now safely link Batch between multiple framework targets!

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

    visionOS

    This version introduces visionOS support. 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

    All deprecated APIs in the SDK v1 have been removed and some others have been renamed/reworked to feel Swift native.

    • The Batch class has been renamed to BatchSDK.

    • Batch has dropped the support for IDFA. You can no longer set an advertising id to Batch and all related APIs have been removed.

    • Removed deprecated method setUseIDFA which was a no-op.

    Push

    • Removed deprecated method setupPush, which was a no-op.

    • Removed the ability to register notification categories using Batch. Please use the .

      • Removed deprecated method registerForRemoteNotifications.

    User

    Most of all user's related APIs have been removed and replaced with their equivalent in the new BatchProfile module. Only the reading methods are still present for backward-compatibility but keep in mind these methods are only about the installation data and not your Profile since it may be updated from another source.

    • Removed deprecated class BatchUserProfile and all related methods/properties.

    • Removed deprecated method trackEvent:withLabel:data and all other variants. You should now use the BatchProfile.trackEvent APIs.

    • Removed method BatchUser.editor and the related class BatchUserDataEditor

    Event

    This version introduced two new types of attribute that can be attached to an event : Array and Object.

    • Renamed BatchEventData class to BatchEventAttributes

    • Removed deprecated method Batch.trackEvent(String event, String label, JSONObject data).

    • Removed all trackEvent APIs from the user module. You should now use Batch.Profile.trackEvent

    Messaging

    • Removed method setDelegate, which has been replaced by the delegate property.

    • Removed method setCanReconfigureAVAudioSession, which has been replaced by the canReconfigureAVAudioSession property.

    • Removed method setAutomaticMode

    Inbox

    • Removed deprecated property title from BatchInboxNotificationContent. You should access it from the message property.

    • Removed deprecated property body from BatchInboxNotificationContent. You should access it from the message property.

    Profile

    Introduced BatchProfile, a new module that enables interacting with profiles. Its functionality replaces most of BatchUser used to do.

    • Added identify API as replacement of BatchUser.editor().setIdentifier.

    • Added editor method to get a new instance of a BatchProfileEditor as replacement of BatchUserDataEditor.

    • Added

    Fixes

    • Fixed an issues where the thread sanitizer would report an issue when opening deeplinks.

    1.21.3


    Compiles with Xcode 15.1 Batch requires Xcode 15.1 and iOS 12.0 or higher

    Core

    • Re-release of 1.21.2 with a valid codesignature. Note: 1.21.3 only exists on SPM & Carthage. The 1.21.2 Cocoapods artifact & manual download has been updated with the new signature.

    1.21.2


    Compiles with Xcode 15.1 Batch requires Xcode 15.1 and iOS 12.0 or higher

    Core

    • Re-release of 1.21.1 due to a build script issue.

    1.21.1


    Compiles with Xcode 15.1 Batch requires Xcode 15.1 and iOS 12.0 or higher

    Core

    • Batch now sets a MinimumOSVersion of 100.0 to workaround a framework validation bug on Xcode 15.3.

    1.21.0


    Compiles with Xcode 15.1 Batch requires Xcode 15.1 and iOS 12.0 or higher

    Core

    • Improved Batch Logger for better use in Xcode 15.

    • Removed support for IDFA (Advertising ID) automatic collection. Batch.setCanUseIDFA is now a no-op. You need to collect it from your side and pass it to Batch via the added [BatchUserDataEditor setAttributionIdentifier:] method.

    • Added Batch's Privacy Manifest. Since, as of writing, Apple does not support the XCPrivacy file for static frameworks you may not see Batch in the genertaed privacy report.

    In-App

    • In-App WebViews can now be inspected using macOS Safari on iOS 16.4 and higher.

    • Fixed a crash when trying to display 0px width images in the Image format.

    1.20.0


    Compiles with Xcode 14.3.1 Batch requires Xcode 14.3.1 and iOS 11.0 or higher

    Note: Due to Xcode 15 dropping support for iOS 11.0, this is the last minor version supporting iOS 11.

    Core

    • Fixed Universal Links not triggering SwiftUI's' onOpenUrl (iOS 14+). For iOS 13, implement your own UISceneDelegate using a custom application delegate or use a BatchDeeplinkDelegate.

    User

    • Added method [BatchUserDataEditor setEmail:]. This requires to have a user identifer registered or to call the setIdentifier method on the editor instance beforehand.

    • Added [BatchUserDataEditor setEmailMarketingSubscriptionState:] method to subscribe to the email marketing list.

    Inbox

    • Added the ability to know if a BatchPushMessage has been displayed from the inbox via the isDisplayedFromInbox property.

    • Added the ability to know if a notification has a landing message attached on BatchInboxNotificationContent using the hasLandingMessage() method.

    • Added the ability to display a landing message attached to a BatchInboxNotificationContent

    Messaging

    • Added support for Dynamic Type allowing Batch to adapt textual content to honor the user's font size settings. This is enabled by default, you can disable it at any moment by using [BatchMessaging setEnableDynamicType:false].

    • In-Apps Banner no longer displays when the presented view controller is an SFSafariViewController as it should not be hidden or obscured by other views or layers.

    1.19.5


    Compiles with Xcode 13.3.1 Batch requires Xcode 13.3.1 and iOS 10.0 or higher

    Note: Due to Xcode 14 dropping support for Bitcode and 32-bit architectures, this is the last minor version to be bundled with these slices.

    Messaging

    • In-App WebView can now display inline <video> elements.

    1.19.4


    Compiles with Xcode 13.3.1 Batch requires Xcode 13.3.1 and iOS 10.0 or higher

    Note: Due to Xcode 14 dropping support for Bitcode and 32-bit architectures, this is the last minor version to be bundled with these slices.

    Core

    • Added support for Universal Links in apps using UISceneDelegate

    Messaging

    • In-App WebView will now ignore WebKit error (204) when loading a video URL without HTML container

    1.19.3


    Compiles with Xcode 13.3.1 Batch requires Xcode 13.3.1 and iOS 10.0 or higher

    Note: Due to Xcode 14 dropping support for Bitcode and 32-bit architectures, this is the last minor version to be bundled with these slices.

    Core

    • Fixed a SQLite crash caused by a race condition: triggering an In-app message with "Re-evaluate campaign eligibility" enabled right after calling [BatchUserDataEditor save] could make concurrent accesses to SQLite and crash.

    Messaging

    • Fixed a rare crash that could happen when re-evaluating multiple campaigns eligibility for a same trigger.

    1.19.2


    Compiles with Xcode 13.3.1 Batch requires Xcode 13.3.1 and iOS 10.0 or higher

    Note: Due to Xcode 14 dropping support for Bitcode and 32-bit architectures, this is the last minor version to be bundled with these slices.

    Core

    • Fixed implementations of various internal objects implementing NSCopying, which could crash in some rare cases on pre iOS 13 devices.

    • Fixed an issue the SDK would not send a notification permission status change to Batch's servers until a later SDK start.

    1.19.1


    Compiles with Xcode 13.3.1 Batch requires Xcode 13.3.1 and iOS 10.0 or higher

    Note: Due to Xcode 14 dropping support for Bitcode and 32-bit architectures, this is the last minor version to be bundled with these slices.

    Core

    • Fixed an issue where some non-profile data (display receipts and In-App campaign view history) wasn't deleted after using [Batch optOutAndWipeData].

    • Updated BatchMessagingContentType's definition to use a modern NS_ENUM declaration. Fixes an issue where the header could not be parsed in an Objective-C++ context.

    Messaging

    • Fixed a display issue on Modals with an image but no title.

    1.19.0


    Compiles with Xcode 13.3 Batch requires Xcode 13 and iOS 10.0 or higher

    Event Dispatchers

    • Added name and version on the BatchEventDispatcherDelegate protocol. There is no need to implement those in your implementations. If you are using a Batch dispatcher plugin, please update it.

    Messaging

    • In-App campaigns are now synchronized on SDK start without delay and will fallback on local cache. This changes the way the "next session" trigger works, which is now equivalent to what used to be "as soon as possible".

    • Added just-in-time verification for In-App campaigns.

    Inbox

    • Added the ability to disable the filtering of silent notifications on BatchInboxFetcher using the filterSilentNotifications property.

    • To support this feature, we deprecated the title and body properties on BatchInboxNotificationContent and moved them under the message property, which is nil for silent notifications. For compatiblity, when filterSilentNotifications is set to false the

    1.18.2


    Compiles with Xcode 13.1 Batch requires Xcode 13 and iOS 10.0 or higher

    Core

    • Updated BatchMessagingContentType's definition to use a modern NS_ENUM declaration. Fixes an issue where the header could not be parsed in an Objective-C++ context.

    Messaging

    • Fixed a display issue on Modals with an image but no title.

    1.18.1


    Compiles with Xcode 13.1 Batch requires Xcode 13 and iOS 10.0 or higher

    Core

    • Batch's XCFramework distribution now has an empty "PrivateHeaders" folder in the Mac Catalyst distribution to satisfy a broken link created by xcodebuild.

    Inbox

    • Fixed an issue where the inbox wouldn't return notifications if the installation was upgraded from an application version running Batch 1.17. Fresh app installs worked as expected.

    1.18.0


    Compiles with Xcode 13.1 Batch requires Xcode 13 and iOS 10.0 or higher

    Starting with this release, Batch can be installed as a XCFramework when using Carthage 0.38 and higher

    Core

    • Added Universal Links support. If your application handles them, please declare your associated domains with [Batch setAssociatedDomains:];. 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.

    Events

    • Added NSURL type for the events data.

    • Batch now emits the BatchEventTrackerFinishedNotification NSNotification when the event tracker has finished sending events. This notification might be triggered multiple times in a row. Read BatchTrackingEventsDidFinishWithSuccessKey in the userInfo to know whether the operation succeeded or failed.

    Attributes

    • Added NSURL type for the custom attributes.

    Inbox

    • Batch will now cache notifications marked as read or deleted to avoid any synchronization issues.

    • Property isDeleted from a BatchInboxNotificationContent is now deprecated: see BatchInbox.h for more info.

    • Fixed an issue where deleting a notification would not be reflected in allFetchedNotifications before re-fetching from the server.

    Debug

    • Batch will now copy the installation ID to the clipboard when the application is foregrounded multiple times in a short timespan. This is enabled by default, you can disable it at any moment by using [Batch setEnablesFindMyInstallation:false].

    1.17.3


    Compiled with Xcode 12.4 Batch requires Xcode 12 and iOS 10.0 or higher

    Core

    • Resolved an issue introduced in 1.17.2 where [Batch optOutWithCompletionHandler:] ignored the developer callback and behaved as if BatchOptOutNetworkErrorPolicyIgnore was returned.

    1.17.2


    Compiled with Xcode 12.4 Batch requires Xcode 12 and iOS 10.0 or higher

    Core

    • Resolved an issue where calling [Batch optOut] or [Batch optOutAndWipeData] could crash if Batch wasn't started.

    • Stopped requesting the Identifier for Vendors (IDFV) from iOS. It was unused since we discontinued Batch Unlock in 2017.

    • IDFA isn't displayed in the debug view anymore, even if IDFA collection is enabled.

    Inbox

    • Fixed an issue where using markAsRead would mark future notifications as read as soon as they arrived.

    • Fix an issue where fetchNewNotifications would behave like fecthNextPage if notifications were already loaded.

    1.17.1


    Compiled with Xcode 12.4 Batch requires Xcode 12 and iOS 10.0 or higher

    Messaging

    • Fix a bug where a user could not interact with the application under a Banner format on iPadOS 14. iPhones were unaffected.

    • Fix a very rare race condition where the SDK could crash in -[BALocalCampaignsManager isEventWatched:].

    1.17.0


    Compiled with Xcode 12.4 Batch requires Xcode 12 and iOS 10.0 or higher

    Core

    • Added the ability to enable Batch's internal logging using [Batch setInternalLogsEnabled:true]. They can also be enabled using the -BatchSDKEnableInternalLogs process argument. Internal logs should be disabled when submitting your app to the App Store.

    • Added nullability annotations to BatchLoggerDelegate. This might trigger a warning in your Swift code.

    Actions

    • Add a "copy to clipboard" builtin action (batch.clipboard).

    • Add a "rate app" builtin action (batch.rating). Requires iOS 10.3 or higher.

    Messaging

    • Added support for a new UI format: WebView. See documentation for more info.

    • Added batchMessageWasCancelledByError: to BatchMessagingDelegate, called when a message could not be loaded due to an error when loading the message's content.

    • Added batchWebViewMessageDidTriggerAction:messageIdentifier:analyticsIdentifier: to BatchMessagingDelegate, called when a message could not be loaded due to an error when loading the message's content.

    Event Dispatchers

    • Added the new event types BatchEventDispatcherTypeMessagingCloseError and BatchEventDispatcherTypeMessagingWebViewClick.

    Push

    • Notification opens, deeplinks and mobile landings are now working when notifications aren't displayed in the foreground (willShowSystemForegroundAlert: false).

    Inbox

    • [BatchInbox fetcherForUserIdentifier:authenticationKey:] now returns a nil instance if "userIdentifier" is nil.

    1.16.1


    Compiled with Xcode 12.4 Batch requires Xcode 12 and iOS 10.0 or higher

    This release backports changes from 1.17.1

    Messaging

    • Fix a bug where a user could not interact with the application under a Banner format on iPadOS 14. iPhones were unaffected.

    • Fix a very rare race condition where the SDK could crash in -[BALocalCampaignsManager isEventWatched:].

    1.16.0


    A migration guide from Batch 1.15 and lower is .

    BREAKING: This version drops support for iOS 8 and 9 Batch requires Xcode 12 and iOS 10.0 or higher

    Batch now depends on libz. This might require a change in your project:

    • Cocoapods: The podspec has been updated to add the required linker flag. No action is needed.

    • Carthage/Manual integration: When building, Xcode should automatically link your project with libz. If you get a compilation error, add -lz to your linker flags, or add libz to "Frameworks and Libraries" in your app's target.

    Batch and Apple Silicon In order to support Apple Silicon, Batch will adopt the XCFramework format: it is not possible to support the iOS Simulator on Apple Silicon macs in a "fat" framework. What it means for you:

    • Cocoapods users will be migrated to the XCFramework automatically

    • Carthage users will stay on the old distribution style until Carthage supports XCFrameworks.

    • Manual integrations should update to XCFramework as soon as possible. Batch will be distributed in both formats for a couple of versions.

    Note that the armv7s slice is not included in the XCFramework distribution.

    BatchExtension

    BatchExtension isn't distributed with the SDK zip anymore. It will be released on github soon after this release.

    Core

    • Batch is now compatible with iOS 14's tracking consent and IDFA changes.

    • Added UIScene support. If your application uses it, you must add an UNUserNotificationCenterDelegate, otherwise Direct Opens, Deeplinks and Mobile Landings will not work: UIScene disables legacy push application delegate methods.

    • eSIMs are now supported. Phones that only have an eSIM will now properly report back their carrier, if the feature hasn't been disabled.

    Event Dispatchers

    • BatchEventDispatcherTypeNotificationOpen is no longer broadcasted when the application is processing a background push.

    Inbox

    • Enhanced the way the SDK fetchs notifications from the servers to greatly reduce bandwidth usage. No public API change.

    Push

    • Add a new method BatchPush.isBatchPush to check if a received push comes from Batch.

    • Added BatchUNUserNotificationCenterDelegate, an UNUserNotificationCenterDelegate implementation that forwards events for Batch. Call BatchUNUserNotificationCenterDelegate.register() to automatically set it as your delegate. BatchUNUserNotificationCenterDelegate can display notifications when your app is in the foreground using the showForegroundNotifications property.

    Messaging

    • The "modal" format now correctly triggers actions after it has been dismissed. This will have an impact on when the custom actions are executed, making them more consistent with the Fullscreen format.

    • This fixes an issue where deeplinks could not be opened in the application using SFSafariViewController with modal formats.

    • The image format is now properly sized when in a freeform window (iPad in Split View, Catalyst)

    • Fix a rare race condition with the interstitial format where it would fail to layout properly when the image server could not be reached.

    User

    • Added new strongly typed methods for setting attributes on BatchUserDataEditor. They greatly enhance Swift usage of the API. See setBooleanAttribute:forKey:error and similar methods (set(attribute: Bool, forKey: String) in Swift).

    • Those new methods return validation errors: you can now know if your attribute key/value does not pass validation and will be discarded.

    • nil values are not supported in the new methods. Use removeAttributeForKey: explicitly.

    Debug

    • Clicking the "share" buttons in the debug views no longer crash on iPads.

    1.15.2


    Compiled with Xcode 11.5 This minor release is the last one to support iOS 8 and 9.

    Event Dispatcher

    • Fix an issue where event dispatchers might not be called.

    User

    • Fix an issue where events that had not been sent to the server would be lost when the app's process was killed.

    1.15.1


    Compiled with Xcode 11.5

    User

    • Added support for Date in BatchEventData.

    • BatchEventData now supports up to 15 attributes (from 10).

    1.15.0


    Compiled with Xcode 11.3.1

    This is the LAST release that support iOS 8 and 9. Future releases will require iOS 10+

    Core

    • Changed how notification status is reported: The SDK will now tell Batch's servers that notifications are enabled if :

    • The app has requested and holds a provisional authorization.

    • The user has disabled banners but kept lockscreen or notification center delivery enabled.

    • Added support for external analytics using BatchEventDispatcher. See the documentation for more details.

    Messaging

    • New enum property BatchMessagingContentType contentType on class BatchInAppMessage to help cast to the right content class.

    • A new optional delegate method BatchMessagingDelegate.presentingViewControllerForBatchUI allows to specify which view controller to display Batch messages on.

    • Fixed an issue where the last line of a label could be cut.

    Inbox

    • Added the markNotificationAsDeleted: method on BatchInboxFetcher, allowing the deletion of notifications

    1.14.2


    This release has been built using Xcode 11.1.

    Messaging

    • Fix an issue where mobile landings might fail to be shown after opening a push for a backgrounded app on iOS 13

    • Fix an issue where BatchDeeplinkDelegate does not fire in certain conditions when opening a push from the lockscreen

    • Fix an issue where banners would move more than expected when swiping on them on iOS 13

    1.14.1


    This release officially supports iOS 13. It has been built using and requires Xcode 11.0 GM 2.

    This is the LAST release that support iOS 8 and 9. Apple has already removed their simulators from Xcode 11.

    Messaging

    • Add UIScene support

    • Add an analytics event for iOS 13's modal swipe-to-dismiss

    1.14.0


    Core

    • Bug fix: deeplinks from actions are properly sent to Deeplink delegate method

    • Fixed an issue where the server-side GDPR wipe event was not sent properly

    • Fixed an issue where the Installation ID may not be wiped properly after a GDPR wipe

    User

    • High level data (language/region/custom user id) can now be read back.

    • User data (attributes and tags) can now be read back.

    Messaging

    • Added support for two new UI formats: Modal, and Image. See the documentation for more information.

    • Added support for GIFs in Mobile Landings and In-App messages

    • Added support for rich text.

    • Added support for text scrolling in all formats. Banners will now have a maximum body height of ~160pt, and their text will scroll.

    Push

    • BatchPushAlertDidResignNotification is sent when user dismissed the Remote notification authorization alert. Notification's userInfo dict contains user's choice in BatchPushNotificationDidAcceptKey

    1.13.2


    Core

    • Fixed a rare race condition crash that could happen when tracking an event while In-App campaigns are fetched from the server.

    User

    • Fixed an issue where adding tags would not work with 1.13.1

    1.13.1


    Re-release of 1.13.0, compiled with Xcode 10.1. Batch now includes an arm64e slice. arm64e bitcode isn't present

    Note: This release comes with an update to the included BatchExtension framework. Its strip-frameworks script will strip the arm64e slice to prevent bitcode-related submission errors: we strongly discourage from enabling arm64e in your extension's Architectures

    1.13.0


    Core

    • Fixed a rare crash that could happen when Batch's internal database failed to initialize in [BAUserDataManager startAttributesSendWSWithDelay:].

    • Opting-out from the SDK now sends an event notifying the server of this. If a data wipe has been asked, the request will also be forwarded to the server. New methods have been introduced to be informed of the status of the request to update your UI accordingly, and possibly revert the opt-out if the network is unreachable.

    • Added the BatchDeeplinkDelegate protocol. Adopting it allows you to manually process deeplink open requests from Batch, rather than having to implement openURL

    Push

    • The SDK will report whether notifications are allowed, denied, or undecided more accurately on iOS 10 or higher

    • Added a method to easily open iOS' settings for the current application's notification settings

    • Split +[BatchPush registerForRemoteNotifications] into more explicit methods.

    Events

    Event data support has been overhauled. As a result:

    • Introduced BatchEventData. Use this class to attach attributes and tags to an event. See this class' documentation for more information about limits.

    • +[BatchUser trackEvent:withLabel:data:] has been deprecated

    • Calls to this method will log deprecation warnings in the console

    1.12.0


    • Added methods to handle opting-out from Batch, and wiping user data. Please see [Batch optOut], [Batch optOutAndWipeData] and [Batch optIn]. You can control whether Batch is opted out from by default setting a boolean TRUE in your Info.plist for the key: BATCH_OPTED_OUT_BY_DEFAULT For compatibility reasons, Batch will be enabled by default. More info in our documentation.

    • Fixed a bug where an In-App Campaign might not trigger for a certain configuration if a tracked event had a label

    1.11.0


    • Added support for Banners in In-App Messaging

      If your app uses the Mobile Landing/In-App Messaging manual mode, and especially [BatchMessaging loadViewControllerForMessage:error:] you need to change the way you present the view controllers manually: please check the for more information. A helper has been added on BatchMessaging, which is especially useful if you don't feel like making your own UIWindow: [BatchMessaging presentMessagingViewController:]

    • Fix In-App Campaigns not honoring the grace period

    • Fix an issue where on iOS 11, the fullscreen Messaging template may not be displayed correctly for themes with no CTA

    1.10.4


    • Added [BatchMessaging presentViewController:], which takes care of showing a previously loaded BatchMessagingViewController in the most appropriate way.

      If you used [BatchMessaging loadViewControllerForMessage:error:] to display messages in manual mode, you should go to the messaging documentation to follow updated instructions: failure to do so will result in incorrect banner behaviour.

    • Added support for Smart Invert in Mobile Landings/In-App Messaging. Images won't be inverted anymore.

    • Added iPhone X support for Mobile Landings/In-App Messaging.

    1.10.3


    • Added a method on BatchInAppMessage, allowing you to fetch the visual content of the message. See BatchInAppMessage's content property for more info

    1.10.2


    • Fixed a bug where overriding a bold font would not work proper for mobile landings

    • Various bugfixes

    1.10.1


    • Removed the one minute minimum delay between two In-App campaign triggers

    • Fixed a bug where In-App Campaigns were sometimes not refreshed when coming back in foreground

    • Fixed a bug where In-App Campaigns failed to trigger on "Next Session"

    • Other bugfixes

    1.10.0


    • Fixed issues found by the Main Thread Checker in Xcode 9

    • Introduced In-App Campaigns

    • Added a Do Not Disturb mode on BatchMessaging, allowing easier control of when landings will be shown

    1.9.0


    • Added the Inbox module, allowing you to fetch previously received notifications from your code. More info: https://batch.com/doc/ios/inbox.html

    1.8.0


    • BREAKING CHANGE: Removed BatchAds and BatchUnlock methods and related classes.

    • Added BatchUser.trackLocation, allowing you to natively track user position updates

    • Deprecated Batch.isRunningInDevelopmentMode. It is useless, as DEV API Keys always start with "DEV"

    • Fix a temporary freeze when opening a push containing a deeplink when triggered by a UNUserNotification delegate

    1.7.4


    • Fix a bug where the delegate set on BatchMessaging was never called.

    1.7.3


    • A bug where landings and open rate tracking didn't work when coming from a cold start in some situations on iOS 10.1/10.2 was fixed. Apps implementing UNUserNotificationCenterDelegate or the "fetchCompletionHandler:" variant of the legacy app delegate callbacks are not concerned.

    • Applying changes with the user data before starting Batch doesn't fail silently anymore, but now logs an error.

    • Improved log clarity for some errors

    1.7.2


    • Rebuild the SDK without debug symbols

    1.7.0


    • Fix the handling of notifications opened while the app is on the screen when using UNUserNotificationCenter on iOS 10. The "bug" remains on iOS 9 because of framework limitations.

    • Introduced

    1.6.0


    • Batch now requires iOS 8 or greater due to Xcode 8 dropping support for earlier versions.

    • [Batch setUseAdvancedDeviceInformation:] has been introduced to reduce the quantity of device information Batch will use. Note that disabling this will limit several dashboard features.

    • Batch will now use the UserNotification framework when possible. UIUserNotificationCategory instances given to [BatchPush setNotificationsCategories:] are automatically converted to UNNotificationCategory on iOS 10.

    • BatchPush has new methods that allows it to be integrated into a UNUserNotificationCenterDelegate instance.

    Since iOS 10 changes quite a lot in how notifications work, it is strongly advised that you read our iOS 10 documentation: https://batch.com/doc/ios/advanced/ios10-migration.html . Upgrading to UNUserNotificationDelegate is recommended.

    1.5.4


    • Fixed push token environment detection (Production/Sandbox) logging

    • Fixes a bug where +[BatchPush trackEvent:withLabel:] didn't track the event label correctly

    1.5.3


    • Removed most of the categories used by the SDK. -ObjC shouldn't be needed anymore, but it is encouraged to keep it

    • Added support for manual integrations: Application Delegate swizzling can now be disabled, but ALL Batch entry points should be implemented carefully. More info on https://batch.com/doc

    • Improved support for notifications containing "content-available":1

    • Added .gitignore in "Batch.bundle" to fix git issues for those who commited Batch itself

    1.5.2


    • Minor bugfixes

    1.5.1


    • Fixed: push token environment detection failed in some rare cases

    1.5


    • Custom user data (attributes, tags and events)

    • Added an API to retrieve Batch's unique installation identifier

    • Deprecated BatchUserProfile

    • HTTP/2 Support (NSURLSession replaces the NSURLConnection backend when available)

    1.4


    • Deprecated Batch Ads

    • Added a method for getting the last known push token

    • Improved push registration logs in case of failure

    • Fixed Xcode 7 compatibility when integrated via CocoaPods NOTE : If you previously integrated Batch, you may need to remove lines that look like "$(PODS_ROOT)/Batch/**" from "Framework Search Paths" in your Build settings for Batch to work with Xcode 7.

    1.3.2


    • Improved Native Ads:

      • Added properties on BatchNativeAd to get image paths on disk (icon and cover)

      • Added a method to load Native Ads without preloading their UIImages

      • Added a MoPub custom event for Native Ads: BatchMoPubCustomNative

    1.3.1


    • Fix an issue that could cause a freeze if your application embedded custom fonts and was started in Airplane mode

    1.3


    • Native Ads

    • Renamed ad methods to avoid confusion between interstitial ads and native ads. Old methods are deprecated but still work

    • Faster interstitial ad display

    • "Batch" Objective-C module now imports everything. Base Batch functionality is in the new "Batch.Core" submodule.

    1.2.6


    • iOS 6 Bugfixes

    • Ads optimisations

    1.2.5


    • Bug fixes

    1.2.4


    • Bug fix

    • Better push implementation

    • Deeplink management improvement

    1.2.3


    • Bug fix

    1.2


    • Batch Ads

    1.1.1 & 1.1.2 & 1.1.3


    • Stability improvements

    1.1


    • Add push capabilities.

    • iOS 8 and Objective-C modules support.

    • Dropped iOS 5 compatibility.

    • BatchUnlockDelegate is now weakly retained, rather than strongly retained.

    1.0.1


    • Move a file put by mistake in /Documents/ into /Library/Application Support/

    1.0


    • Batch release.

    option for In-App component height.
  • Fixed incorrect internal analytics events

  • ,
    batchMessageWasCancelledByUserAction
    ,
    batchMessageWasCancelledByError
    ). Use the new unified
    batchMessageDidDisappear:(NSString *_Nullable)messageIdentifier reason:(BatchMessagingCloseReason)reason
    method.
  • The batchMessageDidTriggerAction delegate method now provides a ctaIdentifier (NSString) instead of the previous actionIndex (NSInteger). BatchMessageGlobalActionIndex's type has been changed accordingly. This method is still called on MEP (Mobile Engagement Platform) messages, but you will now receive an identifier built from the old cta index : “mepCtaIndex:” + index.

  • Renamed property content from class BatchInAppMessage to mepContent. This method will return nil and contentType property will return BatchMessagingContentTypeUnknown for messages coming from the CEP (Customer Engagement Platform).

  • Fixed an issue where dismiss action didn't trigger batchMessageDidTriggerAction

  • Fixed a rare issue where Batch could presents a message on a UIAlertController

  • Strengthened custom user identifier validation on identify API to avoid unintended values.

    Removed method setLoggerDelegate, which has been replaced by the loggerDelegate property.

  • Removed method setAssociatedDomains, which has been replaced by the associatedDomains property.

  • Removed method isOptedOut, which has been replaced by the isOptedOut property.

  • Removed method isRunningInDevelopmentMode with no equivalent.

  • Removed method handleURL with no equivalent.

  • Removed method setUseAdvancedDeviceInformation, you should now use the added updateAutomaticDataCollectionAPI.

  • Renamed method debugViewController to makeDebugViewController.

  • Removed warning about unconfigured App Groups.

  • Added setDisabledMigrations(migrations:)to explicitly disable profile's data migrations when running the SDK V2 for the first time when your app belongs to a project.

  • Added updateAutomaticDataCollection(editor:) to fine-tune the data sent by the SDK since Batch will not resolve the user's location/region and will not send the device type by default.

  • Removed deprecated method registerForRemoteNotificationsWithCategories.

  • Removed method setNotificationsCategories.

  • Removed deprecated method handleNotification.

  • Removed method handleNotification:actionIdentifier.

  • Removed method enableAutomaticDeeplinkHandling, which has been replaced by the enableAutomaticDeeplinkHandling property.

  • Removed method lastKnownPushToken, which has been replaced by the lastKnownPushToken property.

  • Added async variants of BatchPush.requestNotificationAuthorization() and requestProvisionalNotificationAuthorization(). In non async Swift code and Objective-C, they are exposed as variants with a completion handler: BatchPush.requestNotificationAuthorization(completionHandler:)/[BatchPush requestNotificationAuthorizationWithCompletionHandler:]. Note: In async Swift code, this is a breaking change as the language will force you to use the async variant. If you want to ignore the result, use: let _ = try? await BatchPush.requestNotificationAuthorization().

  • Removed pre-iOS 10 UINotification related methods. UNUserNotification and its related callbacks have been the only supported way to integrate Batch for a while as legacy methods lead to unexpected behaviour.

    • Removed deprecated method handleRegisterUserNotificationSettings.

  • Added helpers to extract Batch notification information from UNNotification instances.

    • Added method isBatchNotification

    • Added method deeplinkFromNotification

  • BatchUNUserNotificationCenterDelegate now defaults to showing foreground notifications.

  • , you should now use
    BatchProfile.editor
    which return an instance of
    BatchProfileEditor
    or
    BatchProfile.editWithBlock
    to directly edit attributes in a closure.
  • Removed method printDebugInformation with no equivalent.

  • Removed methods trackTransactionWithAmount and trackTransactionWithAmount:data with no equivalents.

  • Removed methods trackLocation, you should now use BatchProfile.trackLocation.

  • Added BatchUser.clearInstallationData() which allows you to remove the installation data without modifying the current profile.

  • .
  • Removed addTag API from BatchEventData You should now use the $tags reserved key with putStringArray:forKey: method.

  • Removed parameter label from trackEvent APIs. You should now use the $label reserved key in BatchEventAttributes with the putStringt:forKey: method.

  • Added support for Array and Object attributes with the added APIs to BatchEventAttributes :

    • putObject:forKey

    • putObjectArray:forKey

    • putStringArray:forKey

  • Added validateWithError method to BatchEventAttributes to ensure your event attributes are valid before sending them.

  • , which has been replaced by the
    automaticMode
    property.

    Removed deprecated property isDeleted from BatchInboxNotificationContent.

    editWithBlock
    to directly edit profile attributes in a closure. Note: there's no need to save the editor in the closure since it done automatically.
  • Added trackEventWithName and trackEventWithName:attributes APIs as replacement of the BatchUser.trackEvent methods.

  • Added trackLocation API as replacement of the BatchUser.trackLocation method.

  • Added code signature to the Batch's XCFramework distribution.

    using the
    displayLandingMessage()
    method.
    body
    notification content property will be set to the empty string.
  • Added the isSilent property on BatchInboxNotificationContent to identify silent notifications.

  • On iOS 14.5 and higher, when IDFA collection is enabled, Batch will not query iOS for the advertising ID unless the AppTrackingTransparency request has been explicitly approved by the user. This is an optimization: The identifier had never been collected in this case due to iOS blocking it.
  • Modal formats can now be closed with the escape key

  • Fix a bug where cached in-app campaigns might not be deleted when all campaigns were disabled

  • In-App campaign cache is now cleared on server errors

  • Fix an issue where the statusbar color would not be applied

  • More nullability annotations have been added. As those annotations match Apple's own, we do not expect source compatibility to break.
  • Support for TLS versions older than 1.2 has been removed.

  • Added a new builtin action named batch.ios_tracking_consent, which requests tracking consent via AppTrackingTransparency. More info in the documentation.

  • Batch will now emit a warning in the console when your app does not register an UNUserNotificationCenterDelegate by the time application:didFinishLaunchingWithOptions: returns. Implementing this delegate improves Batch's handling of various features that rely on notification interaction feedback, such as analytics or deeplinks: it is strongly recommended that you implement it if you don't already.
  • Batch now emits the BatchPushOpenedNotification NSNotification when a notification has been opened by the user. This deprecates BatchPushReceivedNotification: see BatchPush.h for more info.

  • In automatic mode, application:didReceiveRemoteNotification:fetchCompletionHandler:'s completion handler is no longer called on your behalf when a deeplink is opened.

  • Modally presented messages will not be allowed to be dismissed unless they're the frontmost view controller. This fix an issue where a message with an autodismiss might dismiss a view controller presented on top of it.

  • Improved dismissal logic. While automatic dismissal may fail in some rare occasions due to iOS refusing to dismiss a modal view controller when one is animating, it will not prevent the user from manually dismissing the view anymore.

  • Improved accessibility of all message formats

    Deeplinks can now be open directly in the app using a SFSafariViewController for Push Notifications, Mobile Landings and In-App Mesasges

  • Added new methods on the messaging delegate allowing you to track more information such as close/autoclose and button presses. More info in the Mobile Landings documentation.

  • In swift, BatchMessaging.setAutomaticMode has been renamed to BatchMessaging.setAutomaticMode(on:)

  • . See
    Batch.deeplinkDelegate
    for more information.
    +[BatchPush requestNotificationAuthorization] shows the system notification authorization prompt, and then fetches the push token. Equivalent to calling +[BatchPush registerForRemoteNotifications].
  • +[BatchPush refreshToken] will only ask iOS for a new token. This needs to be called on every application start to handle upgrades from versions without Batch, or if iOS changes the push token.

  • Added support for iOS 12's notification changes:

    • You can now ask for provisional notification authorization using +[BatchPush requestProvisionalNotificationAuthorization]. This method does nothing on versions lower than iOS 12.

    • You can now ask Batch to tell iOS that your app supports opening an in-app notification settings from the system settings by calling [BatchPush setsetSupportsAppNotificationSettings:true] Note that this still requires you to implement a UNUserNotificationCenterDelegate, and the appropriate method to open the settings.

  • Legacy data (NSDictionary) will be converted to BatchEventData. Same data format restrictions apply: Any key/value entry that can't be converted will be ignored, and logged. Tags are not supported
  • Introduced +[BatchUser trackEvent:withLabel:associatedData:] which uses BatchEventData, replacing the deprecated method.

  • Swift users: Since Swift allows methods to be overloaded by type, the method and arguments name do not change: simply replace your data dictionary with a BatchEventData instance Example: BatchUser.trackEvent("event_name", withLabel: "label", data: BatchEventData())

  • The SDK will now log the current Installation ID on start

  • [BatchMessaging loadViewControllerForMessage:error:] is now allowed in automatic mode.
  • Delay button handling in messaging views to until animations are over. This may have prevented deeplinks or custom actions from working properly, as UIKit does not support presenting a view controller while one is already animating.

  • Fix In-App Campaigns accidentally performing too much work on the main thread.

  • Fix a concurrency issue with In-App Campaigns which could very rarely cause a crash

  • Fixed a memory leak with network requests

  • Rewrote documentation with a more modern syntax. Our API docs are now based on jazzy.

  • [BatchPush registerForRemoteNotificationsWithCategories:] has been split in two methods and is now deprecated

  • Introduced a new dynamic framework, BatchExtension. It is compatible with application extensions APIs, and will progressively bring more Batch features to your extensions. In this version, it is used to add support for Rich Notifications.

  • Deprecated [BatchPush setupPush]. It is now implied.

    Added Bitcode support

    Automatic deeplink handling can now be disabled

  • Added a method for getting the deeplink from Batch Push

  • Batch now logs starts for DEV API Keys

    Batch now requires you to link with libsqlite3.dylib

    migration guide
    migration guide
    mergeable dynamic library
    system API
    available here
    Documentation
    updated manual mode documentation
    Mobile Landings