# Troubleshooting Common issues and solutions for Airship SDK setup, initialization, and integration. # Troubleshooting Initialization > Troubleshoot common initialization issues and apply solutions. When following steps in [Getting Started](https://www.airship.com/docs/developer/sdk-integration/apple/installation/getting-started/) or [Advanced Integration](https://www.airship.com/docs/developer/sdk-integration/apple/installation/advanced-integration/), if you don't see a channel ID in the console logs or encounter errors during initialization, review the following common problems and solutions. ## takeOff errors The `takeOff` method throws an error in these cases: - `takeOff` has already been successfully called. - `takeOff` was called without an `AirshipConfig` instance and the SDK could not load or parse `AirshipConfig.plist`. - `takeOff` was called without an `AirshipConfig` instance and the parsed `AirshipConfig.plist` is invalid due to missing credentials. - `takeOff` was called with an `AirshipConfig` instance that has an invalid config due to missing credentials. ## takeOff called multiple times If `takeOff` throws because it has already been successfully called, verify the following: - `takeOff` is only called once per app launch - It's not called in both `application(_:didFinishLaunchingWithOptions:)` and your App's `init()` method - For SwiftUI apps, `takeOff` is called only in the App's `init()` method ## takeOff credentials The `takeOff` method only validates that credentials are present and formatted correctly. It does not verify that the credentials are valid against Airship servers. If the config is properly set up and Airship is only called once, no error will be thrown even if the credentials themselves are invalid. The credentials used by `takeOff` are your Airship project's [App Key](https://www.airship.com/docs/reference/glossary/#app_key) and [App Secret](https://www.airship.com/docs/reference/glossary/#app_secret). To find them, select the dropdown menu (▼) next to your project name, and then **Project details**. **Symptoms of missing or invalid credentials:** - No channel ID appears in the console logs - Warnings or errors in the logs after initialization - Channel is not created in the Airship dashboard If you are experiencing credential issues, do the following: 1. Compare your Airship project credentials with the values in your app, either in code or in `AirshipConfig.plist`. - Credentials must match the expected format and character set. - Credentials must not be empty strings or contain extra whitespace. 1. Ensure both `productionAppKey`/`productionAppSecret` and `developmentAppKey`/`developmentAppSecret` are set before calling `takeOff`. **Guidelines for credentials:** - Use development credentials for development builds and production credentials for release and TestFlight builds. - Configure both development and production credentials in your app, either in code or in `AirshipConfig.plist`. The SDK chooses which to use based on your build configuration. ## AirshipConfig.plist not found or invalid If `takeOff` fails because the SDK could not load or parse `AirshipConfig.plist`, or the plist is invalid, verify the following: - The file exists in your app bundle - The file is included in your target's Copy Bundle Resources build phase - All required keys are present in the plist file: `productionAppKey`, `productionAppSecret`, `developmentAppKey`, and `developmentAppSecret` - The plist file format is valid XML # Troubleshooting Push Notifications > Check push notification status and fix common issues. If [push notifications](https://www.airship.com/docs/developer/sdk-integration/apple/push-notifications/) aren't working as expected, you can check the notification status to diagnose the issue. The SDK provides detailed information about their current state. ## Get Current Notification Status Read the current notification status from `Airship.push.notificationStatus` to inspect each field: #### Swift ```swift let status = await Airship.push.notificationStatus print("User notifications enabled: \(status.isUserNotificationsEnabled)") print("Notifications allowed: \(status.areNotificationsAllowed)") print("Privacy feature enabled: \(status.isPushPrivacyFeatureEnabled)") print("Push token registered: \(status.isPushTokenRegistered)") print("User opted in: \(status.isUserOptedIn)") print("Fully opted in: \(status.isOptedIn)") print("Display status: \(status.displayNotificationStatus)") ``` #### Objective-C > **Note:** This async property is not available in Objective-C. Use the `userPushNotificationsEnabled` property and check authorization status directly with `UNUserNotificationCenter`. ## Listen for Status Changes Use the following to monitor notification status changes in real time: #### Swift ```swift Task { for await status in await Airship.push.notificationStatusUpdates { print("Notification status changed:") print("User opted in: \(status.isUserOptedIn)") print("Fully opted in: \(status.isOptedIn)") } } ``` #### Objective-C > **Note:** This async stream is not available in Objective-C. Use the `userPushNotificationsEnabled` property and check authorization status directly with `UNUserNotificationCenter`. ## Understanding Notification Status Fields The `AirshipNotificationStatus` struct provides detailed information about why push might not be working: | Field | Description | |-------|-------------| | `isUserNotificationsEnabled` | Whether `Airship.push.userPushNotificationsEnabled` is set to `true` | | `areNotificationsAllowed` | Whether the user has granted notification permissions (at least one authorized type) | | `isPushPrivacyFeatureEnabled` | Whether the push privacy feature is enabled in `AirshipPrivacyManager` | | `isPushTokenRegistered` | Whether a push token has been successfully registered with the system | | `displayNotificationStatus` | The system permission status (`.granted`, `.denied`, `.notDetermined`, `.ephemeral`) | | `isUserOptedIn` | `true` if user notifications are enabled, privacy feature is enabled, notifications are allowed, and display status is granted | | `isOptedIn` | `true` if `isUserOptedIn` is `true` AND a push token is registered | ## Common Status Scenarios **Status:** `isUserNotificationsEnabled = false` - **Cause:** `Airship.push.userPushNotificationsEnabled` has not been set to `true`. - **Solution:** Enable user notifications in your app code. **Status:** `areNotificationsAllowed = false` - **Cause:** User denied notification permissions or permissions not yet requested. - **Solution:** Request notification permissions or guide user to system settings. **Status:** `isPushPrivacyFeatureEnabled = false` - **Cause:** Push privacy feature is disabled in Privacy Manager. - **Solution:** Enable the push privacy feature: `Airship.privacyManager.enabledFeatures = [.push]`. **Status:** `isPushTokenRegistered = false` - **Cause:** Device hasn't received a push token from APNs yet. - **Solution:** Check network connectivity, APNs certificate configuration, and device/simulator limitations. **Status:** `isUserOptedIn = true` but `isOptedIn = false` - **Cause:** Push token registration is pending or failed. - **Solution:** Check console logs for APNs registration errors, verify network connectivity, and ensure proper entitlements. # Troubleshooting Notification Service Extensions > Verify notification service extension setup and debug issues. If the basic verification steps do not resolve your [notification service extension](https://www.airship.com/docs/developer/sdk-integration/apple/push-notifications/notification-service-extension/) (NSE) issue, use the advanced debugging steps to isolate the cause. ## Basic verification First, perform basic verifications: 1. Verify the deployment target includes the device you are testing it against. Airship supports 16.0+. ![Deployment target setting in Xcode](https://www.airship.com/docs/images/nse-troubleshoot-deployment-version_hu_5e5f95dfb9a1775e.webp) *Deployment target setting in Xcode* 1. Verify the extension target links the `AirshipNotificationServiceExtension` framework and none of the other Airship frameworks. ![Extension target linked to AirshipNotificationServiceExtension only](https://www.airship.com/docs/images/extension-target-links-airship-notification-service-extension_hu_a3504fa3a2c481b6.webp) *Extension target linked to AirshipNotificationServiceExtension only* 1. Verify the main app target links to your extension. ![Main app target embedding the extension](https://www.airship.com/docs/images/main-app-target-extension_hu_fed870069ef4ef29.webp) *Main app target embedding the extension* 1. Verify the extension bundle ID follows the app bundle ID with a suffix. For example, `com.example.app.NotificationServiceExtension`. ## Advanced debugging If issues persist, isolate the problem by building up from Apple's default implementation: 1. Replace your NSE implementation with Apple's default NSE that only modifies the notification title. If that fails, recreate the extension and repeat the basic verification steps. #### Swift ```swift import UserNotifications class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } } ``` 1. Confirm the extension can modify the title when using Apple's default implementation. 1. Link the Airship NSE framework to your extension and have your NSE extend the Airship NSE class instead of `UNNotificationServiceExtension`. Keep your existing title-modification logic for now. #### Swift ```swift import UserNotifications import AirshipNotificationServiceExtension class NotificationService: UANotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } } ``` 1. Confirm you still receive the modified title. This verifies the Airship NSE framework is linked correctly. 1. Remove the title-modification test code and implement the full Airship NSE integration. #### Swift ```swift import UserNotifications import AirshipNotificationServiceExtension class NotificationService: UANotificationServiceExtension { } ``` 1. Test that rich media, such as images, displays correctly. 1. Add verbose logging to troubleshoot the issue if none of these steps resolved the issue. #### Swift ```swift import UserNotifications import AirshipNotificationServiceExtension class NotificationService: UANotificationServiceExtension { override var airshipConfig: AirshipExtensionConfig { return AirshipExtensionConfig( logLevel: .verbose, logHandler: .publicLogger ) } } ```