Airship iOS SDK 17.x to 18.0 Migration Guide

Xcode requirements

SDK 18.x now requires Xcode 15.2 or newer.

Airship Components

Instead of a mix of class vars and instance vars to access various components on the Airship instance, they have been normalized to just class vars.

SDK 17.x SDK 18.x
Airship.shared.config Airship.config
Airship.shared.actionRegistry Airship.actionRegistry
Airship.shared.permissionsManager Airship.permissionsManager
Airship.shared.javaScriptCommandDelegate Airship.javaScriptCommandDelegate
Airship.shared.channelCapture Airship.channelCapture
Airship.shared.deepLinkDelegate Airship.deepLinkDelegate
Airship.shared.urlAllowList Airship.urlAllowList
Airship.shared.localeManager Airship.localeManager
Airship.shared.privacyManager Airship.privacyManager
Airship.shared.applicationMetrics Removed, this is internal only now

Protocols are exposed instead of concrete classes on Airship to better hide implementation details.

SDK 17.x SDK 18.x
URLAllowList URLAllowListProtocol
AirshipLocaleManager AirshipLocaleManagerProtocol
AirshipPush AirshipPushProtocol
AirshipContact AirshipContactProtocol
AirshipAnalytics AirshipAnalyticsProtocol
AirshipChannel AirshipChannelProtocol

AirshipPush, AirshipContact, AirshipAnalytics, and AirshipChannel are all internal classes now, the shared methods on those classes have been removed. Instead, use the Airship.push, Airship.contact, Airship.analytics, and Airship.channel class vars instead.

NotificationCenter (NSNotificationCenter)

Notification Center events emitted by the Airship SDK have been updated. Most the notifications are still available, except channel updated. The constants for the rest have been moved.

Airship Ready Event

17.x:

    NotificationCenter.default.addObserver(
        forName: Airship.airshipReadyNotification,
        object: nil,
        queue: nil
    ) { notification in
        /// Following values are only available if `extendedBroadcastEnabled` is true in config.
        let appKey = notification.userInfo?[Airship.airshipReadyAppKey] as? String
        let payloadVersion = notification.userInfo?[Airship.airshipReadyPayloadVersion] as? Int
        let channelID = notification.userInfo?[Airship.airshipReadyChannelIdentifier] as? String
    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.AirshipReady.name,
        object: nil,
        queue: nil
    ) { notification in
        /// Following values are only available if `extendedBroadcastEnabled` is true in config.
        let appKey = notification.userInfo?[AirshipNotifications.AirshipReady.appKey] as? String
        let payloadVersion = notification.userInfo?[AirshipNotifications.AirshipReady.payloadVersionKey] as? Int
        let channelID = notification.userInfo?[AirshipNotifications.AirshipReady.channelIDKey] as? String
    }

Channel Created

17.x:

    NotificationCenter.default.addObserver(
        forName: AirshipChannel.channelCreatedEvent,
        object: nil,
        queue: nil
    ) { notification in
        let channelID = notification.userInfo?[AirshipChannel.channelIdentifierKey] as? String
        let isExisting = notification.userInfo?[AirshipChannel.channelExistingKey] as? Bool ?? false
    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.ChannelCreated.name,
        object: nil,
        queue: nil
    ) { notification in
        let channelID = notification.userInfo?[AirshipNotifications.ChannelCreated.channelIDKey] as? String
        let isExisting = notification.userInfo?[AirshipNotifications.ChannelCreated.isExistingChannelKey] as? Bool ?? false
    }

Channel Updated

Channel updated has been removed. For the most part apps should not need that and most likely are trying to listen for opt-in status. For that, see notificationStatus on the PushProtocol.

Received Notifications

The foreground and background notifications have been collapsed into a single event with a userInfo key indicating foreground vs background.

17.x:

    NotificationCenter.default.addObserver(
        forName: AirshipPush.receivedForegroundNotificationEvent,
        object: nil,
        queue: nil
    ) { notification in
        let isForeground = true
        let receivedNotification = notification.userInfo
    }

    NotificationCenter.default.addObserver(
        forName: AirshipPush.receivedBackgrounddNotificationEvent,
        object: nil,
        queue: nil
    ) { notification in
        let isForeground = false
        let receivedNotification = notification.userInfo
    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.RecievedNotification.name,
        object: nil,
        queue: nil
    ) { notification in
        let isForeground = notification.userInfo?[AirshipNotifications.RecievedNotification.isForegroundKey] as? Bool
        let receivedNotification = notification.userInfo?[AirshipNotifications.RecievedNotification.notificationKey] as? [AnyHashable: Any]
    }

Received Notification Response

17.x:

    NotificationCenter.default.addObserver(
        forName: AirshipPush.receivedNotificationResponseEvent,
        object: nil,
        queue: nil
    ) { notification in
        let response = notification.userInfo?[AirshipPush.receivedNotificationResponseEventResponseKey]
    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.ReceivedNotificationResponse.name,
        object: nil,
        queue: nil
    ) { notification in
        let response = notification.userInfo?[AirshipNotifications.ReceivedNotificationResponse.responseKey]
    }

Locale Updated

17.x:

    NotificationCenter.default.addObserver(
        forName: AirshipLocaleManager.localeUpdatedEvent,
        object: nil,
        queue: nil
    ) { notification in
        let locale = notification.userInfo?[AirshipLocaleManager.localeEventKey] as? Locale
    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.LocaleUpdated.name,
        object: nil,
        queue: nil
    ) { notification in
        let response = notification.userInfo?[AirshipNotifications.LocaleUpdated.localeKey] as? Locale
    }

Privacy Manager Updated

17.x:

    NotificationCenter.default.addObserver(
        forName: AirshipPrivacyManager.localechangeEventUpdatedEvent,
        object: nil,
        queue: nil
    ) { _ in

    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.PrivacyManagerUpdated.name,
        object: nil,
        queue: nil
    ) { _ in

    }

Contact Conflict Event

17.x:

    NotificationCenter.default.addObserver(
        forName: AirshipContact.contactConflictEvent,
        object: nil,
        queue: nil
    ) { notification in
        let conflictEvent = notification.userInfo?[AirshipContact.contactConflictEventKey] as? ContactConflictEvent
    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.ContactConflict.name,
        object: nil,
        queue: nil
    ) { notification in
        let conflictEvent = notification.userInfo?[AirshipNotifications.ContactConflict.eventKey] as? ContactConflictEvent
    }

Message Center Updated

17.x:

    NotificationCenter.default.addObserver(
        forName: MessageCenterInbox.messageListUpdatedEvent,
        object: nil,
        queue: nil
    ) { _ in

    }

18.x:

    NotificationCenter.default.addObserver(
        forName: AirshipNotifications.MessageCenterListUpdated.name,
        object: nil,
        queue: nil
    ) { _ in

    }

Adding Events

The Analytics method addEvent(_) has been removed and replaced with recordRegionEvent(_) and recordCustomEvent(_)

SDK 17.x SDK 18.x
Airship.analytics.addEvent(customEvent) Airship.analytics.recordCustomEvent(customEvent)
Airship.analytics.addEvent(regionEvent) Airship.analytics.recordRegionEvent(regionEvent)

AirshipAutomation

The AirshipAutomation module has been rewritten in swift and no longer supports obj-c bindings. For most apps, this will be a trivial update, but if you are using custom display adapters the update will be more extensive. See below for more on custom display adapters.

Accessors

The accessors for InAppMessaging and LegacyInAppMessaging have moved.

SDK 17.x SDK 18.x
InAppAutomation.shared.inAppMessageManager InAppAutomation.shared.inAppMessaging
LegacyInAppMessaging.shared InAppAutomation.shared.legacyInAppMessaging

Cache Management

InAppMessagePrepareAssetsDelegate, InAppMessageCachePolicyDelegate, InAppMessageAssetManager have been removed and is no longer available to extend. These APIs were difficult to use and often times lead to unintended consequences. The Airship SDK will now manage its own assets. External assets required by the App that need to be fetched before hand should happen outside of Airship. If assets are needed and can be fetched at display time, use the CustomDisplayAdapter.waitForReady() method as a hook to fetch those assets.

Display Coordinators

Display coordinators was another difficult to use API that has been removed. Instead, use the InAppMessageDisplayDelegate.isMessageReadyToDisplay(_:scheduleID:) method to prevent messages from displaying, and InAppAutomation.shared.inAppMessaging.notifyDisplayConditionsChanged() to notify when the message should be tried again. If a use case is not able to be solved with the replacement methods, please file a Github issue with your use case.

Extending messages

InAppMessages are no longer extendable when displaying. If this is needed in your application, please file a Github issue with your use case.

Custom Display Adapter

InAppMessageAdapterProtocol has been replaced with CustomDisplayAdapter. The new protocol has changed, but it roughly provides the same functionality as before just with a different interface.

SDK 17.x InAppMessageAdapterProtocol SDK 18.x CustomDisplayAdapter
adapter(for:) No mapping, no required factory method
display() async -> InAppMessageResolution display(scene: UIWindowScene) async -> CustomDisplayResolution
func prepare(with assets: InAppMessageAssets) async -> InAppMessagePrepareResult use isReady and func waitForReady() async. Asset are available in the factory callback
isReadyToDisplay isReady

Example:

final class MyCustomDisplayAdapter : CustomDisplayAdapter {

    @MainActor
    static func register() {
        InAppAutomation.shared.inAppMessaging.setAdapterFactoryBlock(forType: .banner) { message, assets in
            return MyCustomDisplayAdapter(message: message, assets: assets)
        }
    }

    let message: InAppMessage
    let assets: AirshipCachedAssetsProtocol

    init(message: InAppMessage, assets: AirshipCachedAssetsProtocol) {
        self.message = message
        self.assets = assets
    }

    @MainActor
    var isReady: Bool {
        // This is called before the message is displayed. If `false`, `waitForReady()` will
        // be called before this is checked again. If `true`, `display` will  be called
        // on the same run loop
        return true
    }

    @MainActor
    func waitForReady() async {
        /// If `isReady` is false, this method should wait for whatever conditions are required to make `isReady` true.
    }

    @MainActor
    func display(scene: UIWindowScene) async -> CustomDisplayResolution {
        /// Most apps will probably need a continuation
        return await withCheckedContinuation { continuation in

            /// Display the message


            /// Resume with the results after its been displayed. Failing to resume will block other messages
            /// from displaying
            continuation.resume(returning: CustomDisplayResolution.userDismissed)
        }
    }
}

Then, after takeOff:

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {

        ...

        Airship.takeOff(config, launchOptions: launchOptions)
        MyCustomDisplayAdapter.register()

        ...
    }