Troubleshoot Issues on iOS Apps

This article provides information about troubleshooting issues on iOS apps.

App Submission Errors 

App submission errors can occur when you’re publishing your apps to App Store Connect. These errors include an error message from Xcode or during Apple’s app review.

We identified one or more issues with a recent delivery for your app, “[app name]”. Your delivery was successful, but you may wish to correct the following issues in your next delivery:

Missing Purpose String in Info.plist File - Your app’s code references one or more APIs that access sensitive user data. The app’s Info.plist file should contain a NSLocationAlwaysUsageDescription key with a user-facing purpose string explaining clearly and completely why your app needs the data. Starting Spring 2019, all apps submitted to the App Store that access user data will be required to include a purpose string. If you’re using external libraries or SDKs, they may reference APIs that require a purpose string. While your app might not use these APIs, a purpose string is still required. You can contact the developer of the library or SDK and request they release a version of their code that doesn’t contain the APIs. Learn more (https://developer.apple.com/documentation/uikit/core_app/protecting_the_user_s_privacy).

This error can occur even if you aren’t using Location Messaging. If you aren’t using the SDK’s location messaging functionality, your users aren’t prompted to provide location permissions. This error occurs because the SDK includes references to CoreLocation.framework. During the app review process, the automated binary scanning detects these references and requires you to provide purpose strings.

If you aren’t using Location Messaging, add a purpose string to your app’s Info.plist file as a placeholder, as shown in this example.

1<key>NSLocationAlwaysUsageDescription</key>
2<string>Placeholder Purpose String</string>

Apple Push Notification Service Server Configuration 

To ensure the proper configuration of your Apple Push Notification Service (APNs) connection and delivery of push notifications for different types of app builds (development or production), keep these points in mind.

  • APNs Server Selection: If you select the Development option, Marketing Cloud Engagement attempts to contact the Sandbox APNs server, regardless of whether you use a .p8 key or a .p12 certificate. If you select the Production option, Marketing Cloud Engagement attempts to contact the Production APNs server.

  • Apps Built Using Development Certificates: To send notifications only to those devices with apps on Debug configuration, select Development on the MobilePush Administration page in Marketing Cloud Engagement.

    The MobilePush Administration page in Marketing Cloud Engagement, showing the Development option selected for APNs.

  • Apps Built Using Production or Distribution Certificates: To send notifications only to those devices with apps on Release configuration, select Production on the MobilePush Administration page in Marketing Cloud Engagement.

    The MobilePush Administration page in Marketing Cloud Engagement, showing the Production option selected for APNs.

Push Messages Not Displayed 

If you encounter issues receiving messages in your app, consider these troubleshooting steps:

Check the SDK’s Log Output 

The MarketingCloudSDK.framework uses extensive internal logging to record actions performed by the SDK for informational and diagnostic purposes.

General, default-level logging is always enabled. Additionally, the SDK writes error and fault-level logs when certain conditions occur. Add this code to your app to enable logging.

8.x
1// Turn on logging by selecting the logLevel (debug, warn or error). Debugging is not recommended for production apps.
2SFMCSdk.setLogger(logLevel: .debug)

If you use SDK versions older than version 8.x, enable logging after SDK configuration.

7.x
1// turn on logging for debugging.  Not recommended for production apps.
2// Set to true to enable logging while debugging
3MarketingCloudSDK.sharedInstance().sfmc_setDebugLoggingEnabled(true)

You can also enable logging with CustomLogOutputter, as shown in this example.

8.x
1class CustomLogOutputter: LogOutputter {
2    override func out(level: LogLevel, subsystem: String, category: LoggerCategory, message: String) {
3        // custom log outputting code
4    }
5}
6
7// Then set the log level and custom log outputter
8SFMCSdk.setLogger(logLevel: .debug, logOutputter: CustomLogOutputter())

Alternatively, you can enable logging with a standard log output and a log filter, as shown in this example.

8.x
1SFMCSdk.setLogger(logLevel: .debug, logOutputter: LogOutputter(), filters: [.module, .identity])

To clear previously set filtering options, use this code.

8.x
1SFMCSdk.clearLoggerFilters()

To query the state of debug-level logging, use this code.

7.x
1let enabled = MarketingCloudSDK.sharedInstance().sfmc_getDebugLoggingEnabled()

The SDK sends all logging output to Apple’s unified logging system. Review this information using Xcode’s Devices and Simulators window or the macOS Console application. If SDK debug logging is enabled, the SDK uses the OS_LOG_TYPE_DEBUG value. Disable logging before you submit a release build to the App Store.

For more information about unified logging, see Logging on Apple Developer.

Send a Test Push 

To test whether your app can receive a push directly from the Apple Push Notification Service (APNs), follow these steps.

  1. Get the push token from the SDK. For testing and troubleshooting purposes, retrieve your device token from a running app by calling sfmc_deviceToken() and send the result to yourself via email, alert, or another method.

  2. 10.x
    1PushFeature.requestSdk { pushFeature in
    2  print(pushFeature?.deviceToken() ?? "error: no token - was UIApplication.shared.registerForRemoteNotifications() called?")
    3}
    8.x
    1print(SFMCSdk.mp.deviceToken() ?? "error: no token - was UIApplication.shared.registerForRemoteNotifications() called?")
    7.x
    1print(MarketingCloudSDK.sharedInstance().sfmc_deviceToken() ?? "error: no token - was UIApplication.shared.registerForRemoteNotifications() called?")
  3. Trigger the APNs API directly from the command line if using a .p8 authentication key.

    This bash script points to the development APNs service by default. To trigger push from the APNs production server, change the value of the endpoint variable to https://api.push.apple.com.

    Note

    1#!/bin/bash
    2
    3# To get curl with HTTP/2 and openssl with ECDSA: run 'brew install curl-openssl'
    4curl=/usr/local/opt/curl/bin/curl
    5openssl=/usr/local/opt/openssl/bin/openssl
    6
    7--------------------------------------------------------------------------
    8
    9deviceToken=#push token returned from MobilePush SDK
    10authKey=#path to your .p8 extension Auth Key File
    11authKeyId=#the Key ID of the .p8 Auth Key File
    12teamId=#team id found in apple developer account
    13bundleId=#application bundle identifier
    14endpoint=https://api.development.push.apple.com
    15
    16read -r -d '' payload <<-'EOF'
    17{
    18"aps": {
    19    "badge": 1,
    20    "alert": {
    21        "title": "TEST_PUSH_TITLE",
    22        "subtitle": "TEST_PUSH_SUBTITLE",
    23        "body": "TEST_PUSH_BODY"
    24    },
    25    "_sid": "SFMC"
    26}
    27}
    28EOF
    29
    30# --------------------------------------------------------------------------
    31
    32base64() {
    33$openssl base64 -e -A | tr -- '+/' '-_' | tr -d =
    34}
    35
    36sign() {
    37printf "$1"| $openssl dgst -binary -sha256 -sign "$authKey" | base64
    38}
    39
    40time=$(date +%s)
    41header=$(printf '{ "alg": "ES256", "kid": "%s" }' "$authKeyId" | base64)
    42claims=$(printf '{ "iss": "%s", "iat": %d }' "$teamId" "$time" | base64)
    43jwt="$header.$claims.$(sign $header.$claims)"
    44
    45$curl --verbose \
    46--header "content-type: application/json" \
    47--header "authorization: bearer $jwt" \
    48--header "apns-topic: $bundleId" \
    49--data "$payload" \
    50$endpoint/3/device/$deviceToken
  4. If you use a legacy .p12 certificate, trigger the APNs API directly from the command line.

    1#!/bin/sh
    2
    3export DEVICE_TOKEN=#push token returned from MobilePush SDK
    4export BUNDLE_ID=#application bundle identifier
    5export CERT_PATH=#path to your push certificate and key, exported as a p12 file
    6export CERT_PASSWORD=#password of your p12 file
    7
    8# sent your endpoint to prod or sandbox
    9# sandbox MUST use a development push certificate
    10# sandbox MUST use a push token from a debugging build of your app (run from
    11# Xcode to a connected device)
    12export ENDPOINT=https://api.push.apple.com/3/device
    13#export ENDPOINT=https://api.sandbox.push.apple.com/3/device
    14
    15curl -v \
    16-d '{"_m": "TEST_MESSAGE_ID", "aps": {"alert": {"body": "TEST_PUSH_BODY", "title": "TEST_PUSH_TITLE", "subtitle": "TEST_PUSH_SUBTITLE"},"_sid": "SFMC"}}' \
    17-H "apns-topic: ${BUNDLE_ID}" \
    18--http2 \
    19--cert ${CERT_PATH}:${CERT_PASSWORD} \
    20--cert-type P12 \
    21${ENDPOINT}/${DEVICE_TOKEN}

    If your device receives the push notification, the app is configured correctly. The setup includes creating a .p8 authentication key or .p12 certificate, building the app with the correct bundle ID, and using the appropriate provisioning profiles.

    Note

Test Through Marketing Cloud Engagement 

Note the environment (development or production APNs) in which the push was successfully delivered while sending a test push.

On the Engagement Administration page, point to the same environment under Sending Services > iOS Sending and send a push notification. Check if your device received the push notification.

Ensuring the selection of the correct APNs environment is crucial for the SDK to trigger push notifications to the appropriate APNs server. If you select Development on the MobilePush Administration page, Marketing Cloud Engagement contacts the Sandbox APNs server. If you select Production, it contacts the Production APNs server.

Note

Log SDK State Information 

To retrieve a list of all the information the SDK has and its current state, implement getSDKState(), as shown in this example.

8.x
1print("SDK State = \(SFMCSdk.state())")
7.x
1// display a JSON formatted, easily readable block of text describing the current status of the SDK.
2print("SDK State = \(MarketingCloudSDK.sharedInstance().sfmc_getSDKState() ?? "SDK State is nil")")

The SDK outputs a JSON string as shown in this example.

1SDK State = {
2    "Analytics Details" =     {
3        ETAnalyticsEnabled = 1;
4        PIAnalyticsEnabled = 1;
5        PiIdentifier = "Not Set";
6        useLegacyPiIdentifier = 1;
7    };
8    "Inbox Details" =     {
9        "Count of All Inbox Messages in DB" = 0;
10        "Count of Read Inbox Messages in DB" = 0;
11        "Count of Unread Inbox Messages in DB" = 0;
12        "Current Inbox Messages" = None;
13    };
14    "Location General Information" =     {
15        "Beacon Monitoring Details" =         {
16            "Count of Proximity Messages in DB" = 0;
17            "Proximity messages in local DB" = None;
18            "Ranging for Beacons with UUID" = "(null)";
19        };
20        "Geo-fence Monitoring Details" =         {
21            "Count of Geo-fence Messages in DB" = 0;
22            "Currently Monitoring Regions" =             {
23            };
24            "Geo-fence Entry messages in DB" = None;
25            "Geo-fence Exit messages in DB" = None;
26            "Regions in DB" = None;
27        };
28        "Location Details" =         {
29            "Last Known Location" =             {
30                latitude = "0.0";
31                longitude = "0.0";
32            };
33            "Location Entitlement in place" = true;
34            "User Location Authorization Status" = 0;
35            "Watching Location" = 0;
36        };
37    };
38    "MarketingCloudSDK General Information" =     {
39        "Application Name" = "SDKExplorer_Dev";
40        "Application Version" = "Version 216.0.0 (Build 2976)";
41        "Device Details" =         {
42            "Daylight Savings Time Active" = 0;
43            "Device Locale" = "en_US";
44            "Device Reported Timezone" = "-18000";
45            DeviceId = "00000000-0000-0000-0000-000000000000";
46            "Hardware Description" = "iPhone9,4";
47            "OS Version" = "12.1.2";
48            "Platform Name" = iOS;
49        };
50        "MarketingCloudSDK Configuration Details" =         {
51            "Current Configuration" =             {
52                accesstoken = 000000000000000000000000;
53                appid = "00000000-0000-0000-0000-000000000000";
54                etanalytics = 1;
55                inbox = 1;
56                location = 1;
57                name = "DEV";
58                pianalytics = 1;
59                uselegacypiidentifier = 1;
60            };
61        };
62        "MarketingCloudSDK Version" = "6.1.1.0";
63        MarketingCloudSDKLocationDelegate = "Not set";
64        MarketingCloudSDKURLHandlingDelegate = "<SDKExplorer_Dev.AppDelegate: 0x283a72fa0>";
65        "Push Information" =         {
66            "Developer has Push Enabled" = Yes;
67            "Device Token" = 0000000000000000000000000000000000000000000000000000000000000000;
68            "User has Push Enabled" = Yes;
69        };
70        "Registration Details" =         {
71            "Current Registration" =             {
72                "app_version" = "216.0.0";
73                "device_token" = 0000000000000000000000000000000000000000000000000000000000000000;
74                deviceid = "00000000-0000-0000-0000-000000000000";
75                dst = 0;
76                etappid = "00000000-0000-0000-0000-000000000000";
77                hwid = "iPhone9,4";
78                locale = "en_US";
79                "location_enabled" = 0;
80                platform = iOS;
81                "platform_version" = "12.1.2";
82                "proximity_enabled" = 0;
83                "push_enabled" = true;
84                "sdk_version" = "6.1.1.0";
85                tags =                 (
86                    ALL,
87                    DEBUG,
88                    iPhone
89                );
90                timezone = "-18000";
91            };
92            "Date of Last Successful Registration" = None;
93            "Last Successfully Sent Registration" = None;
94        };
95    };
96    "Notifications Details" =     {
97        "Last Notification Received" = None;
98    };
99    "Privacy Settings" =     {
100        "Privacy Mode" = SFMCPrivacyModeNotBlocked;
101    };
102    "Retry Alarms Active" =     (
103        MarketingCloudSDKControlChannel,
104        SFMCRegistration
105    );
106}

Unblock Network Ports 

Testing your app while connected to a corporate network can prevent your test device from receiving messages, especially if your firewall blocks certain ports. Unblock these TCP ports to ensure that your test device can reach the APNs servers.

TCP PortDescription
5223Used by devices to communicate to the APNs servers
2195Used to send notifications to the APNs servers
2196Used by the APNs feedback service
443Used as a fallback service for Wi-Fi devices when those devices can’t communicate with the APNs service on port 5223

Review Additional Troubleshooting Guidance 

Refer to these article when you test your app, or to troubleshoot message delivery issues.

Multiple Push SDKs 

The MobilePush SDK can coexist and work in the same app as the SDKs of other push providers. However, we recommend checking with your other SDK vendors to ensure they also support a multi-push provider implementation. Consider these factors if you implement multiple push SDKs.

Common Issues With Multiple Push Provider (MPP) 

Some MPP implementations can prevent the transmission of device tokens and push messages.

Without device tokens, the SDK doesn’t register the device properly. As a result, Marketing Cloud is unable to send push notifications to the device. The SDK expects you to pass push notifications to the setNotificationRequest method. Using this method makes the SDK aware of the notifications and lets it handle them accordingly.

Apple has specific delegate methods that the consuming application must implement to register with APNs and receive push notifications. Implementing multiple push providers can affect the previously mentioned functionality since other vendors can provide wrapper methods for registration and receiving the notifications. Consuming applications often listen to the wrapper methods instead of actual Apple-provided delegate methods. An example of a common issue is when registration with Apple is done for one push provider without setting the deviceToken for other push providers.

Handling MPP with the MobilePush SDK 

Method Swizzling is the process of changing the implementation of a selector at runtime. If Method Swizzling is enabled, other push providers automatically intercept all the application delegate methods, which differ from the normal flow in setting up the deviceToken and notification userinfo.

Method Swizzling can confuse MobilePush SDK users about how and where to set the SDK’s required API methods because another provider is changing the implementation without their knowledge.

To determine if other SDK providers use Swizzling, check to see if they use these methods.

  • func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
  • func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)

MobilePush SDK users can handle MPP implementations regardless of whether Swizzling is enabled.

With Swizzling Enabled 

If Swizzling is enabled, implement the other push provider’s delegate methods and then implement the MobilePush SDK methods.

First, configure the SDK along with the other Push provider, as shown in this code example.

10.x
1func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
2    if let options = launchOptions, let notification = options[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
3        self.notificationUserInfo = notification
4    }
5
6    FirebaseApp.configure()
7    Messaging.messaging().delegate = self
8    self.configureSdk()
9
10    if #available(iOS 10.0, *) {
11        UNUserNotificationCenter.current().delegate = self
12
13        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
14        UNUserNotificationCenter.current().requestAuthorization(
15            options: authOptions,
16            completionHandler: { _, _ in }
17        )
18    } else {
19        let settings: UIUserNotificationSettings =
20        UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
21        application.registerUserNotificationSettings(settings)
22    }
23    application.registerForRemoteNotifications()
24
25    return true
26}
27
28@discardableResult
29func configureSdk() -> Bool {
30    let appID = "<your appID here>"
31    let accessToken = "<your accessToken here>"
32    let appEndpointURL = "<your appEndpoint here>"
33    let mid = "<your account MID here>"
34
35#if DEBUG
36    SFMCSdk.setLogger(logLevel: .debug)
37#endif
38
39    let appEndpoint = URL(string: appEndpointURL)!
40
41    var configBuilder = ConfigBuilder()
42
43    let engagementConfiguration = MarketingCloudSdkConfigBuilder(appId: appID)
44        .setAccessToken(accessToken)
45        .setMarketingCloudServerUrl(URL(string: appEndpointURL)!)
46        .setMid(mid)
47        .setInboxEnabled(true)
48        .setLocationEnabled(true)
49        .setAnalyticsEnabled(true)
50        .build()
51
52    configBuilder = configBuilder
53        .setEngagement(config: engagementConfiguration)
54
55    let pushFeatureConfiguration = PushFeatureConfigBuilder()
56        .setApplicationControlsBadging(true)
57        .build()
58
59    configBuilder = configBuilder
60        .setPushFeature(config: pushFeatureConfiguration)
61
62    // Set the completion handler to take action when all modules initialization is completed.
63    // Seting the completion handler is optional.
64
65    let completionHandler: ((_ status: [ModuleInitStatus]) -> Void) = { [weak self] status in
66        DispatchQueue.main.async {
67            self?.handleSDKInitializationCompletion(status: status)
68        }
69    }
70
71    SFMCSdk.initializeSdk(configBuilder.build(), completion: completionHandler)
72
73    return true
74}
75
76// MARK: - SDK Initialization Completion Handler
77
78private func handleSDKInitializationCompletion(status: [ModuleInitStatus]) {
79    var allSuccessful = true
80
81    for moduleStatus in status {
82        print("Module: \(moduleStatus.moduleName.rawValue), Status: \(moduleStatus.initStatus.rawValue)")
83
84        if moduleStatus.initStatus == .success {
85            // Handle successful initialization for each module
86            switch moduleStatus.moduleName {
87            case .engagement:
88                // Handle successful initialization for Marketing cloud module
89            case .pushFeature:
90                // Handle successful initialization for Push Feature module
91            default:
92                break
93            }
94        } else if moduleStatus.initStatus == .error {
95            allSuccessful = false
96            // module failed to initialize, check logs for more details
97        } else if moduleStatus.initStatus == .cancelled {
98            allSuccessful = false
99            // module initialization was cancelled (for example due to re-confirguration triggered before init was completed)
100        } else if moduleStatus.initStatus == .timeout {
101            allSuccessful = false
102            // module failed to initialize due to timeout, check logs for more details
103        }
104    }
105    if allSuccessful {
106        print("SDK initialization completed successfully")
107    } else {
108        print("SDK initialization completed with errors - check logs above")
109    }
110}
8.x
1func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
2    if let options = launchOptions, let notification = options[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
3        self.notificationUserInfo = notification
4    }
5
6    FirebaseApp.configure()
7    Messaging.messaging().delegate = self
8    self.configureSFMCSdk()
9
10    if #available(iOS 10.0, *) {
11        UNUserNotificationCenter.current().delegate = self
12
13        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
14        UNUserNotificationCenter.current().requestAuthorization(
15            options: authOptions,
16            completionHandler: { _, _ in }
17        )
18    } else {
19        let settings: UIUserNotificationSettings =
20        UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
21        application.registerUserNotificationSettings(settings)
22    }
23    application.registerForRemoteNotifications()
24
25    return true
26}
27
28@discardableResult
29func configureSFMCSdk() -> Bool {
30    let appID = "<your appID here>"
31    let accessToken = "<your accessToken here>"
32    let appEndpointURL = "<your appEndpoint here>"
33    let mid = "<your account MID here>"
34
35#if DEBUG
36    SFMCSdk.setLogger(logLevel: .debug)
37#endif
38
39    let appEndpoint = URL(string: appEndpointURL)!
40
41    let mobilePushConfiguration = PushConfigBuilder(appId: appID)
42        .setAccessToken(accessToken)
43        .setMarketingCloudServerUrl(appEndpoint)
44        .setMid(mid)
45        .setInboxEnabled(true) // enable if needed by your application
46        .setLocationEnabled(true) // enable if needed by your application
47        .setAnalyticsEnabled(true) // enable if needed by your application
48        .build()
49
50    let completionHandler: (OperationResult) -> () = { result in
51        if result == .success {
52            self.setupMobilePush()
53        } else if result == .error {
54        } else if result == .cancelled {
55        } else if result == .timeout {
56        }
57    }
58
59    SFMCSdk.initializeSdk(ConfigBuilder().setPush(config: mobilePushConfiguration, onCompletion: completionHandler).build())
60
61    return true

Next, configure the SDK to set the device token, as shown in this code example.

10.x
1// MARK: FireBaseMessaging Delegate
2/**
3 Set deviceToken to MarketingCloudSDK in the FCM delegate method when Swizzling is enabled.
4 DeviceToken must be set MANDATORILY to MarketingCloudSDK using`SFMCSdk.mp.setDeviceToken` API for Push notifications to be received through Mobile Push.
5 */
6extension AppDelegate : MessagingDelegate {
7    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
8        print("FCM Token", fcmToken ?? "")
9
10        PushFeature.requestSdk { pushFeature in
11            print("SDK is operational")
12            if let apnsToken = messaging.apnsToken {
13                print("Setting APNs token in MarketingCloudSDK")
14                pushFeature?.setDeviceToken(apnsToken)
15            } else {
16                print("fcm token is null")
17            }
18            print("SDK is not yet operational")
19        }
20    }
21}
8.x
1// MARK: FireBaseMessaging Delegate
2/**
3 Set deviceToken to MarketingCloudSDK in the FCM delegate method when Swizzling is enabled.
4 DeviceToken must be set MANDATORILY to MarketingCloudSDK using`SFMCSdk.mp.setDeviceToken` API for Push notifications to be received through Mobile Push.
5 */
6extension AppDelegate : MessagingDelegate {
7    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
8        print("FCM Token", fcmToken ?? "")
9
10        SFMCSdk.requestPushSdk { mp in
11            print("SDK is operational")
12            if let apnsToken = messaging.apnsToken {
13                print("Setting APNs token in MarketingCloudSDK")
14                mp.setDeviceToken(apnsToken)
15            } else {
16                print("fcm token is null")
17            }
18            print("SDK is not yet operational")
19        }
20    }
21}

When Swizzling is enabled in the other push provider, respective delegate methods are intercepted. For example, if Firebase is the other push provider, when a push notification is received from Firebase, the payload received in the UNUserNotificationCenterDelegate’s didReceive notification method is altered to receive a MessagingMessageInfo object. Because the payload doesn’t match the format that the SDK expects, the message isn’t reported.

API:

  • setNotificationUserInfo(userInfo)
  • setNotificationRequest(response.notification.request)
10.x
1/**
2     MobilePush SDK: REQUIRED IMPLEMENTATION
3     */
4    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
5        PushFeature.requestSdk { pushFeature in
6            pushFeature?.setNotificationUserInfo(userInfo)
7        }
8        completionHandler(.newData)
9    }
10
11    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
12        PushFeature.requestSdk { pushFeature in
13            pushFeature?.setNotificationResponse(response)
14        }
15        completionHandler()
16    }
17
18    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
19        completionHandler([.banner, .list, .sound])
20    }
8.x
1/**
2     MobilePush SDK: REQUIRED IMPLEMENTATION
3     */
4    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
5        SFMCSdk.requestPushSdk { mp in
6            mp.setNotificationUserInfo(userInfo)
7        }
8        completionHandler(.newData)
9    }
10
11    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
12        SFMCSdk.requestPushSdk { mp in
13            mp.setNotificationRequest(response.notification.request)
14        }
15        completionHandler()
16    }
17
18    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
19        completionHandler([.banner, .list, .sound])
20    }

Notification messages from other providers are displayed in the device’s notification center. However, any action on the notification message from the SDK (for example, URL handling and reporting) doesn’t work.

Note

With Swizzling Disabled 

To disable Swizzling, refer to the other push provider’s documentation. When Swizzling is disabled in the other push provider, the default AppDelegate methods are called.

Implement AppDelegate Methods

10.x
1// Disable Swizzling in the other push provider used
2//registerForRemoteNotifications() method gets to this callback
3func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
4    PushFeature.requestSdk { pushFeature in
5        pushFeature?.setDeviceToken(deviceToken)
6    }
7}
8
9/**
10 MobilePush SDK: REQUIRED IMPLEMENTATION
11 */
12func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
13    PushFeature.requestSdk { pushFeature in
14        pushFeature?.setNotificationUserInfo(userInfo)
15    }
16    completionHandler(.newData)
17}
18
19func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
20    PushFeature.requestSdk { pushFeature in
21        pushFeature?.setNotificationResponse(response)
22    }
23    completionHandler()
24}
25
26func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
27    completionHandler([.banner, .list, .sound])
28}
8.x
1// Disable Swizzling in the other push provider used
2//registerForRemoteNotifications() method gets to this callback
3func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
4    SFMCSdk.requestPushSdk { mp in
5        mp.setDeviceToken(deviceToken)
6    }
7}
8
9/**
10 MobilePush SDK: REQUIRED IMPLEMENTATION
11 */
12func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
13    SFMCSdk.requestPushSdk { mp in
14        mp.setNotificationUserInfo(userInfo)
15    }
16    completionHandler(.newData)
17}
18
19func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
20    SFMCSdk.requestPushSdk { mp in
21        mp.setNotificationRequest(response.notification.request)
22    }
23    completionHandler()
24}
25
26func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
27    completionHandler([.banner, .list, .sound])
28}

Common MPP Implementation Issues 

MPP implementation issues are often caused by issues related to device registration, badging, geolocation, custom payload keys, and feedback.

  • Registration: You must only make one call to a push SDK to register for push notifications. Otherwise, a single push notification can trigger multiple notification banners, alerts, or sounds. The MobilePush SDK gives the app developer the power to register.

  • Notification Settings: An app can invoke requestAuthorizationWithOptions and registerForRemoteNotifications multiple times. However, only the settings from the last call are used, as each successive call overwrites the previous settings.

  • Badging: There’s no way to guarantee the value of a badge.

  • Custom Payload Keys: If your implementation must distinguish between two notification providers, use custom keys or other payload-specific data to ensure that your app calls the correct SDK handler that supports multiple notification handlers. Passing a third party’s notification to setNotificationRequest or setNotificationUserInfo is essentially a no-op call. The SDK only emits logs indicating the origin of the notification wasn’t from Engagement.

  • Geolocation: If you implement multiple SDKs that use location-enabled services, use only one SDK’s location enablement. Using more than one leads to unknown and unsupportable consequences. For example, the methods used by the other providers to interact with iOS CoreLocation services and enable location services are likely to affect each provider. An app can monitor a limited number of geofences at any given time. This number depends on iOS version, device type, and other considerations. With multiple implementations competing for a limited resource, the user experience can suffer. Additionally, permissions needed to use location-enabled SDKs can overlap or conflict.

  • Feedback: Not all providers are able to detect if a device has been unregistered. To see how notifications are handled using Firebase as a push provider, see the iOS LearningApp.

iOS Data Protection 

iOS Data Protection affects the SDK as described in this table.

iOS Data Protection LevelSDK Behavior
No protectionSDK works in the foreground and background
Complete until first user authenticationSDK works in the foreground and background after the first unlock
Complete unless openSDK works in the foreground and background after the first unlock
CompleteSDK works only in the foreground after the device is unlocked

The MobilePush SDK requires access to files on the iOS Device file system. Some iOS Data Protection modes can prevent the SDK from accessing the needed files at certain times. During normal operation, the SDK must have access to files while running in the foreground and while running in the background. If the SDK can’t access these files due to an iOS Data Protection mode, an error is logged and file access fails.

By default, the SDK sets the file protection type to NSFileProtectionCompleteUntilFirstUserAuthentication. With this file protection type, files are stored in an encrypted format on disk and can’t be read from or written to until the user unlocks the device for the first time. As of version 8.0.9, the SDK continues to retain the default protection type (NSFileProtectionCompleteUntilFirstUserAuthentication). However, it also provides the capability to override the file protection type within the consuming application. You can choose different file protection types, such as NSFileProtectionComplete and NSFileProtectionCompleteUnlessOpen, based on your specific needs.

NSSQLiteErrorDomain errors can appear in logs if the FileProtectionType is NSFileProtectionComplete and the application goes to the background.

Note

Override FileProtectionType 

The following example shows how you can override the file protection type.

8.x
1SFMCSdk.setFileProtectionType(fileProtectionType: .completeUntilFirstUserAuthentication)

For an additional example of overriding the file protection type, see the learning application.

Configuration Issues 

If you configure iOS data protection such that the file system isn’t accessible during the SDK configuration call, the SDK retries the call for up to 5 seconds. This retry period allows time for a user to unlock the device and make the file system accessible. If the user doesn’t unlock the device within 5 seconds, an error object is returned describing the error, and the configuration call fails, returning false. In this case, the SDK isn’t configured, and no access to SDK methods must be attempted until the configure method returns true. The error object returns the error code configureDatabaseAccessError.

Foreground and Background Operation Issues 

Certain features of the SDK require access to the file system as the app transitions to the foreground or background. Foreground operations include retrieving messages from Engagement for inbox messaging, location messaging, and sending analytic information back to Engagement. Background operations include sending analytic information back to Engagement. If any of these features are enabled via configuration, then an appropriate iOS Data Protection mode must be selected for them to work correctly.

Troubleshoot Data Merge 

For iOS applications upgraded from SDK versions 7.x to versions up to 8.0.6, the previous v7.x tags and attributes are retained on the device but not sent to the server. If an application doesn’t reset or regenerate tags and attributes, the device sends empty tags and attributes to the system.

The following sections walk through the requirements for merging datasets successfully.

Upgrade the SDK 

Using SPM, upgrade to the latest version of the MobilePush SDK for iOS and SFMCSDK for iOS.

Swift Compilation Error 

You may encounter a build failure if the Swift compiler version used to build the SDK doesn’t match the version used by your current Xcode toolchain.

The build fails with this error:

1failed to build module 'PushFeatureSDK'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 6.1.2...', while this compiler is 'Apple Swift version 6.2.3...'). Please select a toolchain which matches the SDK.
2
3'PushFeatureProtocol' is unavailable: cannot find Swift declaration for this protocol.

To resolve this issue, follow these steps to reset your environment:

  1. Clear Derived Data.
  2. Remove the SDK dependencies from your project and then add them back again.
  3. Clean and Run.

Merge Data (Optional) 

The merging tool offers two options to merge attributes and tags: automatic merging and manual merging. In some scenarios, you can choose to defer or avoid merging the datasets. The merging tool defaults to an “opted out” state if you don’t implement either of the merging methods. In the opted out state, tags and attributes aren’t merged from the version 7.x dataset to your current application’s dataset.

Automatic Merging 

The automatic merging option attempts to merge old data into the current dataset, with the current data taking precedence over data within the version 7.x dataset.

The following tables illustrate how automatic merging behaves and how data is merged.

in this tables, key:value pairs are denoted using : as the separator.

Note

Attributes

Prior DatasetCurrent DatasetMerge Result
A:BemptyA:B
emptyA:BA:B
A:B, C:DA:EA:E, C:D
A:BA: clearedA: cleared

Tags

Prior DatasetCurrent DatasetMerge Result
SHIRTSemptySHIRTS
emptyPANTSPANTS
SHIRTSPANTSSHIRTS, PANTS
SHIRTSSHIRTS, PANTSSHIRTS, PANTS

The following code snippets show you how to configure the SDK to attempt an automatic merge.

To ensure the completion callback passed into setAutoMergePolicy is set before SDK initialization, place the following code snippets before SDK initialization.

Note

Swift
1SFMCSdk.setAutoMergePolicy { isMergeSucces in
2    if(!isMergeSuccess) {
3        // ...
4    }
5}
Objective-C
1[SFMCSdk setAutoMergePolicyOnCompletion:^(BOOL isMergeSuccess) {
2    if(!isMergeSuccess) {
3        // ...
4    }
5}];

Manual Merging 

The manual merge option enables you to receive both the prior data and the current data in a callback, providing the opportunity to choose what data ultimately ends up in the final dataset.

You can decide what attributes and tags are set in the current dataset. However, to set attributes and tags accordingly, you must retain the data until the SDK is initialized.

Access to the tags and attributes for versions 7.x and 8.x is provided before SDK initialization.

Important

Swift
10.x
1class ExampleDelegate: UIResponder, UIApplicationDelegate {
2
3    var setTagsAndAttributes: (() -> ())? = nil
4
5    // ...
6}
7
8// ...
9SFMCSdk.setManualMergePolicy(withHandler: {(v8, v9) in
10
11    self.setTags = {
12        let tags: [String] = // e.g. v9["tags"]
13        MarketingCloudSdk.requestSdk { mc in
14            mc?.addTags(tags)
15        }
16    }
17}
18                             
19private func handleSDKInitializationCompletion(status: [ModuleInitStatus]) {
20    // ...
21    if moduleStatus.initStatus == .success {
22        // Handle successful initialization for each module
23        switch moduleStatus.moduleName {
24        case .engagement:
25            self.setTags?()
26            // Handle successful initialization for Marketing cloud module
27        }
28    }
29    // ...
30}
31
32// ...
33
34SFMCSdk.initializeSdk(configBuilder.build(), completion: completionHandler)
8.x
1class ExampleDelegate: UIResponder, UIApplicationDelegate {
2
3    var setTagsAndAttributes: (() -> ())? = nil
4
5    // ...
6}
7
8// ...
9SFMCSdk.setManualMergePolicy(withHandler: {(v7, v8) in
10
11    self.setTagsAndAttributes = {
12        let attributes: [String:String] = // e.g. v8["attributes"] as! [String : String]
13        SFMCSdk.identity.setProfileAttributes([ModuleName.push : attributes])
14
15        let tags: [String] = // e.g. v8["tags"]
16        SFMCSdk.requestPushSdk { mp in
17            mp.addTags(tags)
18        }
19    }
20}
21
22// ...
23
24let completionHandler: (OperationResult) -> () = { result in
25    // ...
26    if result == .success {
27        // ...
28        if (SFMCSdk.mp.getStatus() == .operational) {
29            self.setTagsAndAttributes?()
30        }
31        // ...
32    }
33}
34
35// ...
36
37SFMCSdk.initializeSdk(ConfigBuilder().setPush(config: configuration, onCompletion: completionHandler).build())
Objective C
10.x
1@interface ExampleDelegate : UIResponder <UIApplicationDelegate>
2
3@property (nonatomic, copy) void (^setTags)(void);
4
5// ...
6
7[SFMCSdk setManualMergePolicyWithHandler:^(NSDictionary * _Nonnull v8, NSDictionary * _Nonnull v9) {
8    self.setTags = ^{
9    
10        NSArray *tags = // e.g. v9[@"tags"];
11        [SFMarketingCloudSdk requestSdk:^(id<MarketingCloudSdkInterface> _Nonnull mp) {
12          [mp addTags: tags];
13        }];
14    }
15}];
16
17// MARK: - SDK Initialization Completion Handler
18
19- (void)handleSDKInitializationCompletion:(NSArray<SFMCModuleInitStatus *> *)status {
20    // ...
21        if (moduleStatus.initStatus == SFMCSdkOperationResultSuccess) {
22            switch (moduleStatus.moduleName) {
23                case SFMCSdkModuleNameEngagement:
24                    if (self.setTags != nil) {
25                        [self setTags];
26                    }
27                    break;
28            }
29        }
30    // ...
31
32SFMCSdkConfigBuilder *configBuilder = [[SFMCSdkConfigBuilder alloc] init];
33configBuilder = [configBuilder setEngagementWithConfig:pushConfig];
34[SFMCSdk initializeSdk:[configBuilder build] completion:^(NSArray<SFMCModuleInitStatus *> * _Nonnull status) {
35    // Handle completion
36    [self handleSDKInitializationCompletion:status];
37}];
8.x
1@interface ExampleDelegate : UIResponder <UIApplicationDelegate>
2
3@property (nonatomic, copy) void (^setTagsAndAttributes)(void);
4
5// ...
6
7[SFMCSdk setManualMergePolicyWithHandler:^(NSDictionary * _Nonnull v7, NSDictionary * _Nonnull v8) {
8    self.setTagsAndAttributes = ^{
9        NSDictionary *attributes = // e.g. v8[@"attributes"];
10        [[SFMCSdk identity] setProfileAttributes:attributes];
11
12        NSArray *tags = // e.g. v8[@"tags"];
13        [SFMCSdk requestPushSdk:^(id<PushInterface> _Nonnull mp) {
14          [mp addTags: tags];
15        }];
16    }
17}];
18
19// ...
20
21void (^completionHandler)(OperationResult) = ^(OperationResult result) {
22   switch(result) {
23       case OperationResultSuccess:
24            // ...
25            if ([[SFMCSdk mp] getStatus] == ModuleStatusOperational) {
26                if (self.setTagsAndAttributes != nil) {
27                    [self setTagsAndAttributes];
28                }
29            }
30           // ...
31
32           break;
33       // ...
34   }
35}
36
37SFMCSdkConfigBuilder *configBuilder = [[SFMCSdkConfigBuilder alloc] init];
38configBuilder = [configBuilder setPushWithConfig:pushConfig onCompletion:completionHandler];
39[SFMCSdk initializeSdk:[configBuilder build]];

Retry Data Merge 

If you must run the merge tool again, you can attempt the merge multiple times.

Reattempting merges doesn’t roll back the current dataset but enables you to regain access to the data within the old version 7.x dataset.

Swift
1let appId = // your app ID
2let resetSuccess: Bool = SFMCSdk.resetDataPolicy(appId: appId)
3if (resetSuccess) {
4    print("reset succeeded")
5}
Objective-C
1NSString *appId = // your app ID
2BOOL resetSuccess = [SFMCSdk resetDataPolicyWithAppId:appId];
3if(resetSuccess) {
4    NSLog(@"reset succeeded");
5}

Keychain Crash 

This troubleshooting guidance applies only to MobilePush SDK for iOS versions 8.0.13 and earlier.

Important

Fatal Keychain Access exceptions occur if an application with an active MobilePush SDK integration tries to access Keychain on a locked device secured with Face ID or passcode-based authentication. iOS Data Protection modes prevent the SDK from accessing these files.

These sections describe common Keychain Access exceptions and provide guidance on resolving them.

Common errSecInteractionNotAllowed Exceptions 

These are common occurrences of the errSecInteractionNotAllowed exception:

  • Fatal Exception: com.salesforce.security.keychainException dictionaryItemFromKeychain: Error attempting to look up keychain item: errSecInteractionNotAllowed
  • Fatal Exception: com.salesforce.security.keychainException setObject:forKey:: Error saving value to the keychain: errSecInteractionNotAllowed.
  • Fatal Exception: com.salesforce.security.keychainException writeToKeychain: Error adding keychain item: errSecInteractionNotAllowed.

To resolve errSecInteractionNotAllowed exceptions, follow these steps.

  • Upgrade to MobilePush SDK for iOS version 8.0.8 or later and Marketing Cloud SDK version 1.0.6 or later.

  • Before initializing the SDK, set the setKeychainAccessErrorsAreFatal method to false.

    1SFMCSdk.setKeychainAccessErrorsAreFatal(errorsAreFatal: false)

    Setting this property to false logs the exception to the console instead of causing the app to crash.

Unknown Status Code (-34018) 

You can encounter an error message that indicates a keychain access issue along with an unknown status code (-34018).

1Terminating app due to uncaught exception 'com.salesforce.security.keychainException',
2reason: 'dictionaryItemFromKeychain: Error attempting to look up keychain item:
3Unknown status code (-34018)'

To resolve this issue, enable Keychain Sharing in the Signing & Capabilities pane in Xcode. When you enable Keychain Sharing, you don’t need to add identifiers specific to the SDK.

Related Items 

For an example of an ideal SDK implementation on iOS, see the iOS LearningApp.

Not all the code used in the LearningApp is required. Depending on your app, you can decide which parts to use or replicate and which ones to ignore.

MarketingCloudSDK.bundle Inclusion Crash 

These troubleshooting steps apply only to the MobilePush SDK for iOS version 8.0.13 and earlier. In version 8.1.0 and later, the SDK automatically includes the bundle.

Important

When you upgrade to the latest version of the SDK, update MarketingCloudSDK.bundle. If you don’t copy the right versions, your app can crash because the older versions of MarketingCloudSDK.bundle don’t have the required resources.

If you don’t include the latest MarketingCloudSDK.bundle in your app, you can encounter these exceptions.

NSInvalidArgumentException

Reason: +entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'SFMCEndpointConfigurationEntity'

NSInternalInconsistencyException

Reasons:

  • 'NSFetchRequest could not locate an NSEntityDescription for entity name 'SFMCEventConfigurationEntity''
  • 'Cannot create an NSPersistentStoreCoordinator with a nil model'

To resolve these exceptions, upgrade to the latest version of the SDK and then follow these steps.

  1. Remove the existing MarketingCloudSDK.bundle from Xcode under Build phases > Copy Resources Bundle.
  2. Based on your integration method, add the latest MarketingCloudSDK.bundle.

Silent Push Notification 

Silent push notifications are delivered without an alert message or sound, typically to trigger updates to the app UI or background operations. A silent push notification wakes your app from a “Suspended” or “Not Running” state to update content or run certain background tasks without notifying users.

To use silent push notifications to trigger background tasks, configure your app to receive notifications even when it’s in the background. To do so, navigate to the Signing & Capabilities pane on Xcode, and add the Background Modes capability to the main app target. Also ensure that you select the Remote notifications checkbox.

To handle silent push notifications, implement the application:didReceiveRemoteNotification:fetchCompletionHandler: application delegate method. For more information, see Apple’s documentation on Pushing background updates to your app.

Troubleshoot Push Delivery Analytics 

If you encounter issues with push delivery analytics, consider these common problems and their resolutions:

Missing Push Delivery Analytics

  • First confirm that push delivery analytics is enabled in the Engagement UI. If disabled, your Engagement admin must enable it for the SDK to send delivery events.
  • If push delivery events and analytics aren’t appearing in your Engagement reports, verify whether you’re sending supported message types. Push delivery analytics are only available for Push and Alert + Inbox messages. In-App, Geofence, Proximity, and silent pushes aren’t tracked.
Multiple Push Provider Implementation Problems

If you’re using multiple push providers, ensure that no fields or keys are removed from the push payload before passing it to SFMCNotificationService, which your NotificationService class in the service extension inherits from. The SDK requires the complete push payload to process push delivery events. If any keys or fields are missing, the SDK logs a warning and doesn’t process the push delivery event.

See Also