UpdateManager lets an iOS application receive remotely published update and maintenance decisions. It connects to the managed UpdateManager service, caches the last compatible configuration, and can either present its built-in SwiftUI interface automatically or return decisions for presentation on demand.
- iOS 15 or later
- Swift 6 and Xcode 16 or later
- An application and public key created in the UpdateManager dashboard
- A published configuration for the environment used by the app
The application public key is an identifier, not a secret. It is safe to embed in an application binary.
In Xcode, select File > Add Package Dependencies, enter the UpdateManager
package repository URL, and add the UpdateManager product to the application
target.
Then import the module where it is used:
import UpdateManagerAutomatic presentation is the recommended setup for most SwiftUI apps.
Configure UpdateManager once during application startup and attach
.updateManager() to the root view.
import SwiftUI
import UpdateManager
@main
struct MyApp: App {
init() {
UpdateManager.shared.configure(
UpdateManagerConfiguration(
publicKey: "pub_your_application_key",
analyticsEnabled: false
)
)
}
var body: some Scene {
WindowGroup {
RootView()
.updateManager()
}
}
}The modifier checks the published policy when its root view starts and whenever the app returns to the active state. It presents:
- A dismissible screen for optional updates when dismissal is allowed.
- A non-dismissable screen for required updates.
- A non-dismissable maintenance screen with a retry action.
- Nothing when the current app version requires no action.
Update status is controlled by the published server policy. If a network request fails, UpdateManager safely uses compatible cached configuration when the selected fetch policy permits it. A failure never invents a required update or maintenance state.
Use manual presentation when the app should decide when to check, has custom update UI, or needs tighter control over navigation and multi-scene behavior.
Configure the SDK with manual presentation and do not add .updateManager() to
the root view:
@main
struct MyApp: App {
init() {
UpdateManager.shared.configure(
UpdateManagerConfiguration(
publicKey: "pub_your_application_key",
presentationMode: .manual,
analyticsEnabled: false
)
)
}
var body: some Scene {
WindowGroup {
RootView()
}
}
}Call checkForUpdates() whenever the app wants to evaluate the current policy:
Button("Check for Updates") {
Task {
do {
let decision = try await UpdateManager.shared.checkForUpdates()
switch decision {
case .none:
print("The app is up to date")
case .optional(let content):
print("Optional update: \(content.latestVersion)")
case .required(let content):
print("Required update: \(content.latestVersion)")
case .maintenance(let content):
print("Maintenance: \(content.title)")
}
} catch {
print("Update check failed: \(error.localizedDescription)")
}
}
}The following example evaluates the policy when the user taps a button and presents UpdateManager's built-in SwiftUI views:
import SwiftUI
import UpdateManager
struct SettingsView: View {
@State private var decision: UpdatePresentationDecision = .none
@State private var errorMessage: String?
var body: some View {
Button("Check for Updates") {
Task { await checkForUpdates() }
}
.alert("Update Check Failed", isPresented: .constant(errorMessage != nil)) {
Button("OK") { errorMessage = nil }
} message: {
Text(errorMessage ?? "")
}
.fullScreenCover(isPresented: isPresentingUpdate) {
switch decision {
case .optional(let content):
OptionalUpdateView(content: content) {
decision = .none
}
case .required(let content):
RequiredUpdateView(content: content)
case .maintenance(let content):
MaintenanceView(content: content) {
await checkForUpdates()
}
case .none:
EmptyView()
}
}
}
private var isPresentingUpdate: Binding<Bool> {
Binding(
get: { decision != .none },
set: { isPresented in
if !isPresented { decision = .none }
}
)
}
@MainActor
private func checkForUpdates() async {
do {
decision = try await UpdateManager.shared.checkForUpdates()
} catch {
errorMessage = error.localizedDescription
}
}
}checkForUpdates() evaluates the real published policy. It does not force an
update screen when the current version is not eligible for one.
Apps can fetch the latest configuration for feature flags or custom behavior without presenting an update screen:
do {
let result = try await UpdateManager.shared.fetchConfiguration(
policy: .useCacheIfFresh
)
print("Configuration revision: \(result.configuration.revision)")
print("Source: \(result.source)")
} catch {
print("Configuration fetch failed: \(error.localizedDescription)")
}Production is the default environment:
UpdateManager.shared.configure(publicKey: "pub_your_application_key")To use another dashboard environment explicitly:
UpdateManager.shared.configure(
UpdateManagerConfiguration(
publicKey: "pub_your_application_key",
environment: .staging,
analyticsEnabled: false
)
)The selected environment must have a published configuration. Otherwise the
SDK reports UpdateManagerError.configurationNotPublished.
Before shipping an app:
- Confirm that the public key belongs to the correct iOS application.
- Publish a configuration for the production environment.
- Provide a valid App Store URL before publishing a required update.
- Test optional, required, maintenance, and no-update states.
- Keep analytics disabled until analytics collection is enabled for the UpdateManager service.
- Prefer manual presentation for apps with multiple independent scenes or a custom navigation architecture.
Verify that the public key is correct and publish the configuration for the same environment configured in the SDK.
Confirm that the dashboard application is configured for iOS. Required updates must include a valid App Store URL.
Confirm that the device can reach https://api.updatemanager.dev. For support,
capture the SDK error and the request ID available through diagnostics; never
log session tokens or other application credentials.