Quickstart by stack
iOS Quickstart
Install the Swift SDK with Swift Package Manager, read a flag from SwiftUI, and target users with context.
Prerequisites
- iOS 13 or later, Xcode 15 or later, and Swift 5.9 or later.
- A mobile key (prefixed mob_) or a client key (prefixed cli_) from your environment. Server keys (srv_) are rejected when you create the SDK, because anything bundled in an app can be extracted.
- A flag key you can test in a non-production environment.
dependencies: [
.package(url: "https://github.com/zenmanage/zenmanage-swift.git", from: "1.0.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "Zenmanage", package: "zenmanage-swift")
]
)
]
In Xcode, choose File → Add Package Dependencies, paste https://github.com/zenmanage/zenmanage-swift.git, and add the Zenmanage library to your app target.
import Zenmanage
let zenmanage = try Zenmanage(environmentToken: "mob_your_mobile_key_here")
let flag = try await zenmanage.flags().single("new-dashboard", defaultValue: false)
if flag.isEnabled() {
showNewDashboard()
} else {
showOldDashboard()
}
Safe defaults
The default you pass to single is what you get when the flag doesn't exist or the rules can't be loaded, such as an offline device with nothing cached or a wrong key. Rules are fetched once and evaluated on the device, so reading a flag never waits on a per-flag network call.
One instance per app
Create a single Zenmanage at launch and pass it down. It keeps the loaded rules in memory, so build it once instead of per screen.
import SwiftUI
import Zenmanage
struct DashboardView: View {
let zenmanage: Zenmanage
@State private var showNewDashboard = false
var body: some View {
Group {
if showNewDashboard {
NewDashboardView()
} else {
LegacyDashboardView()
}
}
.task {
let flag = try? await zenmanage.flags().single("new-dashboard", defaultValue: false)
showNewDashboard = flag?.isEnabled() ?? false
}
}
}
let context = Context(
type: "user",
name: "Taylor",
identifier: "user-123",
attributes: [
Attribute("country", values: ["US"]),
Attribute("plan", values: ["pro"]),
]
)
let flag = try await zenmanage.flags()
.withContext(context)
.single("premium-dashboard", defaultValue: false)
let variant = try await zenmanage.flags()
.withContext(context)
.single("checkout-flow", defaultValue: "control")
switch variant.asString() {
case "one-page":
showOnePageCheckout()
default:
showMultiPageCheckout()
}
let config = try await zenmanage.flags()
.single("advanced-config", defaultValue: ["retries": 3, "timeout-ms": 5000])
.asJson()
Stable percentage rollouts
The SDK hashes the rollout salt and your context identifier into a bucket from 0 to 99, so a user stays in or out of a rollout across launches. Raising the percentage only adds users. Pass a stable identifier, such as your user ID, in the context.
Cached in UserDefaults
Rules are cached in UserDefaults for an hour by default, so a cold launch evaluates flags from the cache without a network call until it expires. Pass cacheTTL to change the lifetime, or swap in InMemoryRuleCache() or NullRuleCache() through the cache parameter.
Mobile or client keys only
Use a mob_ or cli_ key in the app. A srv_ key throws when you create the SDK. Keys in an app bundle are readable by anyone with the binary, so keep server keys on your backend.
Swift Package Manager only
The SDK ships through Swift Package Manager. There is no CocoaPods podspec. A bridge for Objective-C is included, so mixed-language apps can import the same package.
Next step
Take the next integration step in your own stack.
Start with the quickstart that matches your runtime, then return to the reference pages when you need exact request and payload details.
Language and framework names and logos shown above are trademarks or registered trademarks of their respective owners. Their use here is for identification purposes only and does not imply endorsement of, or affiliation with, Zenmanage.