Airship iOS SDK 16.x to 17.0 Migration Guide
Xcode requirements
SDK 17.x now requires Xcode 14.3 or newer.
Minimum deployment version
SDK 17.x is compatible with iOS 14+. Apps using Airship will need to update the minimum deployment version.
Removed modules
The following modules are no longer supported and have been removed from the SDK:
AirshipAccengage
Users of Accengage should remove the AirshipAccengage
module from their project after completing the migration process.
For further information about migration and removal, see the Accengage Migration guide.
AirshipChat
The Airship Chat module is no longer supported and has been removed from the SDK.
AirshipLocation
The Airship Location module is no longer supported and has been removed from the SDK. If you want to continue prompting users for location permissions, you must update your integration to set a location permission delegate on the PermissionsManager
:
import Foundation
import CoreLocation
import AirshipCore
import Combine
class LocationPermissionDelegate: AirshipPermissionDelegate {
let locationManager = CLLocationManager()
@MainActor
func checkPermissionStatus() async -> AirshipCore.AirshipPermissionStatus {
return self.status
}
@MainActor
func requestPermission() async -> AirshipCore.AirshipPermissionStatus {
guard (self.status == .notDetermined) else {
return self.status
}
guard (AppStateTracker.shared.state == .active) else {
return .notDetermined
}
locationManager.requestAlwaysAuthorization()
await waitActive()
return self.status
}
var status: AirshipPermissionStatus {
switch(locationManager.authorizationStatus) {
case .notDetermined:
return .notDetermined
case .restricted:
return .denied
case .denied:
return .denied
case .authorizedAlways:
return .granted
case .authorizedWhenInUse:
return .granted
@unknown default:
return .notDetermined
}
}
}
@MainActor
private func waitActive() async {
var subscription: AnyCancellable?
await withCheckedContinuation { continuation in
subscription = NotificationCenter.default.publisher(for: AppStateTracker.didBecomeActiveNotification)
.first()
.sink { _ in
continuation.resume()
}
}
subscription?.cancel()
}
Then after takeOff, register the permission delegate for the location permission:
Airship.shared.permissionsManager.setDelegate(
LocationPermissionDelegate(),
permission: .location
)
AirshipExtendedActions
The Airship Extended Actions only contained the RateAppAction which is now available in the core module.
Allowed URLs
The URL allow list configuration has been changed to an opt-out process, rather than an opt-in process like previous SDK versions.
By default, all URLs are allowed by SDK 17, unless explicitly disallowed by the app via the urlAllowList
or urlAllowListScopeOpen
config options.
Allow list behavior changes:
- If neither
urlAllowList
orurlAllowListScopeOpenURL
are set in your Airship config, the SDK will default to allowing all URLs and an error message will be logged. - To suppress the error message, set
urlAllowList
orurlAllowListScopeOpenURL
to[*]
to your config to adopt the new allow-all behavior, or customize the allowed URLs as needed. - URLs for media displayed within in-app messages will no longer be checked against the URL allow lists.
- YouTube has been removed from the default allow list. If your application makes use of opening links to YouTube from Airship messaging, you will need to update your allow list to explicitly allow
youtube.com
, or allow all URLs with[*]
.
Renamed classes
Some common class names have been renamed to prevent collisions with other libraries/apps:
Legacy class name | New class name |
---|---|
Config | AirshipConfig |
Channel | AirshipChannel |
Contact | AirshipContact |
Push | AirshipPush |
PrivacyManager | AirshipPrivacyManager |
Analytics | AirshipAnalytics |
Action | AirshipAction |
Situation | ActionSituation |
Features | AirshipFeature |
Event | AirshipEvent |
In-App Automation
Updated the default display interval for In-App Messages
The new default display interval for in-app messages is now set to 0 seconds. Apps that wish to maintain the previous default display interval of 30 seconds should set the display interval manually, after takeOff:
InAppAutomation.shared.inAppMessageManager.displayInterval = 30.0
Deep link delegate
Deep link delegate is now async.
SDK 16:
deepLinkDelegate.receivedDeepLink(deepLink) {
completionHandler(true)
}
SDK 17:
await deepLinkDelegate.receivedDeepLink(deepLink) {
completionHandler(true)
}
Contacts
Contact conflict listener interface updated
Contact conflict event is now available as a NSNotification or using Airship.contact.conflictEventPublisher
to listen for events:
SDK 16:
conflictDelegate?.onConflict(anonymousContactData: anonData, namedUserID: namedUserID)
SDK 17:
Airship.contact.conflictEventPublisher.sink { event in
// ...
}
NotificationCenter.default.addObserver(
self,
selector: #selector(conflictEventReceived),
name: AirshipContact.contactConflictEvent
)
Async Named User ID access
Named User ID access is now an async property:
SDK 16:
let namedUserID = Airship.contact.namedUserID
SDK 17:
let namedUserID = await Airship.contact.namedUserID
Async Subscription lists access
Subscription list is now an async method:
SDK 16:
Airship.contact.fetchSubscriptionLists { contactSubscriptionLists, error in
// Use the contactSubscriptionLists
}
SDK 17:
let contactSubscriptions = try await Airship.contact.fetchSubscriptionLists
Channels
Async Subscription lists access
Subscription list is now an async method:
SDK 16:
Airship.channel.fetchSubscriptionLists { channelSubscriptionLists, error in
// Use the channelSubscriptionLists
}
SDK 17:
let channelSubscriptions = try await Airship.channel.fetchSubscriptionLists
Live Activities
The API’s to track and restore live activity tracking have been updated to no longer require a Task:
SDK 16:
Task {
await Airship.channel.trackLiveActivity(
activity,
name: "order-1234"
)
Task {
await Airship.channel.restoreLiveActivityTracking { restorer in
await restorer.restore(
forType: Activity<DeliveryAttributes>.self
)
await restorer.restore(
forType: Activity<SomeOtherAttributes>.self
)
}
}
SDK 17:
Airship.channel.trackLiveActivity(
activity,
name: "order-1234"
)
Airship.channel.restoreLiveActivityTracking { restorer in
await restorer.restore(
forType: Activity<DeliveryAttributes>.self
)
await restorer.restore(
forType: Activity<SomeOtherAttributes>.self
)
}
Message Center
The MessageCenter module has been rewritten in Swift and the OOTB UI in SwiftUI. With the rewrite, we are providing a new set of APIs that take advantage of Swift’s structured concurrency.
Message listing API Changes
All methods on the Message Center for listing are now async, and the listing is no longer stored in memory.
Accessing message list
SDK 16:
let messages = MessageCenter.shared.messageList.messages
SDK 17:
let messages = await MessageCenter.shared.inbox.messages
Deleting a message
SDK 16:
MessageCenter.shared.messageList.markMessagesDeleted([message]) { // completed }
SDK 17:
// by message ID
await MessageCenter.shared.inbox.delete(messageIDs: ["messageID"])
// by message
await MessageCenter.shared.inbox.delete(messages: [message])
Marking a message as read
SDK 16:
MessageCenter.shared.messageList.markMessagesRead([message]) { // completed }
SDK 17:
// by message ID
await MessageCenter.shared.inbox.markRead(messageIDs: ["messageID"])
// by message
await MessageCenter.shared.inbox.markRead(messages: [message])
Refreshing the message listing
SDK 16:
MessageCenter.shared.messageList.retrieveMessageList(successBlock: {
// handle success
}, withFailureBlock: {
// handle failure
})
SDK 17:
await MessageCenter.shared.inbox.refreshMessages()
Listening to message listing updates
SDK 16:
NotificationCenter.default.addObserver(self,
selector: #selector(messageListWillUpdate),
name: UAInboxMessageListWillUpdateNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(messageListUpdated),
name: UAInboxMessageListUpdatedNotification,
object: nil)
SDK 17:
MessageCenter.shared.inbox.messagePublisher
.receive(on: RunLoop.main)
.sink(receiveValue: { messages in
// Latest messages
})
.store(in: &self.subscriptions)
Message Center UI
Now our Message Center View has been rewritten using SwiftUI. The UIKit based views have been removed.
Embedding Message Center in SwiftUI
struct CustomMessageCenter: View {
let controller = MessageCenterController()
var body: some View {
MessageCenterView(controller: controller)
}
}
Embedding Message Center in UIKit
SDK 16:
import AirshipKit
class MessageCenterViewController : DefaultMessageCenterSplitViewController {
}
SDK 17:
// 1
let messageCenterviewController = MessageCenterViewControllerFactory.make(
controller: controller
)
if let messageCenterView = messageCenterviewController.view {
// 2
// Add the message center view controller to the destination view controller.
addChild(messageCenterviewController)
view.addSubview(messageCenterView)
// 3
// Create and activate the constraints.
messageCenterView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
messageCenterView.topAnchor.constraint(equalTo: view.topAnchor),
messageCenterView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
messageCenterView.leftAnchor.constraint(equalTo: view.leftAnchor),
messageCenterView.rightAnchor.constraint(equalTo: view.rightAnchor),
])
}
Theming
MessageCenterStyle
has been renamed to MessageCenterTheme
to avoid confusing with SwiftUI style patterns.
SDK 16:
let style = MessageCenterStyle()
MessageCenter.shared.defaultUI.style = style
SDK 17:
var messageCenterTheme = MessageCenterTheme()
MessageCenter.shared.theme = messageCenterTheme
You can also set the theme on the MessageCenterView directly:
Example:
MessageCenterView(controller: controller)
.messageCenterTheme(CustomMessageCenter.messageCenterTheme)
Load custom message center message view
Fetch user credentials:
SDK 16:
MessageCenter.shared.user.getData { user in
// ...
}
SDK 17:
let user = await MessageCenter.shared.inbox.user
Load webView:
The example below shows how to fetch the credentials, set auth on the request, and load a message into the webview.
This code assumes a custom view controller with an embedded WKWebView, as well as a MessageCenterMessage
ready to be loaded.
SDK 16:
let requestObj = NSMutableURLRequest(url:message.messageURL)
MessageCenter.shared.user.getData { data in
// set the auth
let auth = Utils.authHeaderString(withName: data.username, password: data.password)
requestObj.setValue(auth, forHTTPHeaderField:"Authorization")
// load the request
self.webView.load(requestObj)
}, queue: DispatchQueue.main)
SDK 17:
var request = URLRequest(url: message.bodyURL)
let user = await MessageCenter.shared.inbox.user
// set the auth
request.setValue(user.basicAuthString, forHTTPHeaderField: "Authorization")
// load the request
self.webView.load(request)
Preference Center
Preference Center UI
The Preference Center UI has been rewritten in SwiftUI.
Embedding Preference Center in SwiftUI
PreferenceCenterView(preferenceCenterID: "preferenceCenter-ID")
Embedding Preference Center in UIKit
// 1
let preferenceCenterviewController = PreferenceCenterViewControllerFactory.makeViewController(preferenceCenterID: "neat")
if let preferenceCenterView = preferenceCenterviewController.view {
// 2
// Add the prefrence center view controller to the destination view controller.
addChild(preferenceCenterviewController)
view.addSubview(preferenceCenterView)
// 3
// Create and activate the constraints.
preferenceCenterView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
preferenceCenterView.topAnchor.constraint(equalTo: view.topAnchor),
preferenceCenterView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
preferenceCenterView.leftAnchor.constraint(equalTo: view.leftAnchor),
preferenceCenterView.rightAnchor.constraint(equalTo: view.rightAnchor),
])
}
Theme
PreferenceCenterStyle
has been replaced by PreferenceCenterTheme
.
SDK 16:
let style = PreferenceCenterStyle()
PreferenceCenter.shared.style = style
SDK 17:
var theme = PreferenceCenterTheme()
PreferenceCenter.shared.theme = theme
A theme can also be set directly on the PreferenceCenterView:
PreferenceCenterView(preferenceCenterID: "preferenceCenterID")
.preferenceCenterTheme(theme)
Actions
Actions have been rewritten to use async/await and be Sendable.
Registering actions
SDK 16:
Airship.shared.actionRegistry.register(action, names: ["action_name", "action_alias"])
SDK 17:
Airship.shared.actionRegistry.registerEntry(names: ["action_name", "action_alias"]) {
return ActionEntry(action: action)
}
Defining actions
SDK 16:
let customAction = BlockAction { args, completionHandler in
print("Action is performing with args: \(args)")
completionHandler(ActionResult.empty())
}
SDK 17:
let customAction = BlockAction { args in
print("Action is performing with args: \(args)")
return nil
}
Running actions
SDK 16:
// Run an action by name
ActionRunner.run("action_name", value: "action_value", situation: .manualInvocation) { result in
print("Action finished!")
}
// Run an action directly
ActionRunner.run(action, value: "action_value", situation: .manualInvocation) { result in
print("Action finished!")
}
SDK 17:
// Run an action by name
let result = await ActionRunner.run(
actionName: "action_name",
arguments: ActionArguments(
string: "action_value",
situation: .manualInvocation
)
)
// Run an action directly
let result = await ActionRunner.run(
action: action,
arguments:ActionArguments(
string: "action_value",
situation: .manualInvocation
)
)