Skip to main content

iOS SDK

Segment membership​

This package only evaluates published targeting lists. It has no membership write API and must not hold a Backend application key.

Update members from your backend. Call membership from your backend SDK, not from the iOS app. Flow.

Frontend telemetry​

Use iOS SDK 1.5.2 or later for compact frontend telemetry across Core, SwiftUI, UIKit, and Combine. Configure it on TogglyConfig; explicit events are async and available on an owned TogglyService or Toggly.shared.

import TogglyCore

// Run from an async application task; retain this owner for its full lifetime.
let service = TogglyService(config: TogglyConfig(
appKey: "YOUR_APP_KEY",
environment: "Production",
enableTelemetry: true,
metricsBaseUrl: "https://metrics.toggly.io",
telemetryFlushIntervalMs: 45_000
))

await service.initialize()
await service.recordUsage("new-checkout")
await service.recordView("new-checkout", variant: "blue")
await service.incrementCounter("orders", value: 2)
await service.setGauge("cart_total", value: 12.5)
await service.flushTelemetry()
await service.setAppState(.background)
await service.dispose() // only when this owner ends

SwiftUI, UIKit, and Combine count the feature values they actually evaluate or present. Forward application backgrounding and dispose directly owned services; the SDK bounds both operations. See the frontend telemetry contract.

Use Toggly's native iOS SDK in Swift applications for iOS, macOS, tvOS, and watchOS.

Quick reference

Grab the printable Swift / iOS cheat sheet (download PDF) — SwiftUI property wrappers, UIKit, Combine, signed defs.

Session identity​

Set instanceId in TogglyConfig. On the same service, call await service.setIdentity("user-123", instanceId: "minted-token") or await service.setInstanceId("minted-token"). setInstanceId(nil) clears the token; setIdentity(nil) also clears it and returns to the device identity. Core and Toggly.shared expose these async APIs; UI and Combine integrations must reuse their existing service rather than constructing another reporter. See session attribution.

Overview​

The iOS SDK is a modular feature flags solution built entirely in Swift with async/await support. It provides SwiftUI property wrappers, UIKit extensions, and Combine publishers for seamless integration.

Packages​

PackageDescription
TogglyCoreCore functionality, storage, and API client (required)
TogglySwiftUISwiftUI property wrappers, view modifiers, and views
TogglyUIKitUIKit extensions and view controller support
TogglyCombineCombine publishers for reactive patterns

Requirements​

  • iOS 14.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+
  • Swift 5.5+
  • Xcode 15.0+

Installation​

Use the published ios-sdk-v1.5.2 tag or later. This tag includes the root Swift Package Manager manifest; earlier tags without that manifest cannot be installed directly from the repository URL.

Swift Package Manager​

Use the prefixed release tag as a Git revision. Add the package to your Package.swift:

dependencies: [
.package(url: "https://github.com/ops-ai/Toggly.FeatureManagement.git", revision: "ios-sdk-v1.5.2")
]

Then add the products you need:

.target(
name: "YourApp",
dependencies: [
.product(name: "TogglyCore", package: "Toggly.FeatureManagement"),
.product(name: "TogglySwiftUI", package: "Toggly.FeatureManagement"),
.product(name: "TogglyUIKit", package: "Toggly.FeatureManagement"),
.product(name: "TogglyCombine", package: "Toggly.FeatureManagement"),
]
)

Xcode​

  1. File → Add Package Dependencies
  2. Enter: https://github.com/ops-ai/Toggly.FeatureManagement.git
  3. Open the published ios-sdk-v1.5.2 tag and copy the full commit SHA it points to.
  4. Choose the Commit dependency rule and enter that full commit SHA.
  5. Select the products you need (TogglyCore is always required).

Quick Start​

Initialize the SDK​

import TogglyCore

@main
struct MyApp: App {
init() {
// Configure with your Toggly app key
Toggly.configure(config: TogglyConfig(
appKey: "your-app-key",
environment: "Production"
))

// Initialize asynchronously
Task {
await Toggly.shared.initialize()
}
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

SwiftUI Usage​

import TogglySwiftUI

struct ContentView: View {
// Use the @FeatureFlag property wrapper
@FeatureFlag("new-dashboard") var isNewDashboardEnabled

var body: some View {
VStack {
if isNewDashboardEnabled {
NewDashboardView()
} else {
LegacyDashboardView()
}

// Or use the view modifier
Text("Beta Feature")
.featureFlag("beta-feature")

// Or use FeatureView for cleaner conditional rendering
FeatureView("welcome-banner") {
WelcomeBanner()
}
}
}
}

UIKit Usage​

import TogglyUIKit

class MyViewController: FeatureFlagViewController {
override func viewDidLoad() {
super.viewDidLoad()
observeFeature("new-dashboard")
}

override func featureFlagDidChange(_ key: String, isEnabled: Bool) {
// Update UI based on feature flag changes
newDashboardView.isHidden = !isEnabled
}
}

Combine Usage​

import TogglyCombine

class ViewModel: ObservableObject {
@Published var isFeatureEnabled = false
private var cancellables = Set<AnyCancellable>()

init() {
TogglyPublishers.featureFlag("new-dashboard")
.receive(on: DispatchQueue.main)
.assign(to: &$isFeatureEnabled)
}
}

Async/Await Usage​

import TogglyCore

// Simple feature check
let isEnabled = await Toggly.shared.isFeatureOn("my-feature")

// Feature gate with multiple features
let allEnabled = await Toggly.shared.evaluateFeatureGate(
featureKeys: ["feature-a", "feature-b"],
requirement: .all
)

Variants​

Set enableVariants: true on TogglyConfig to fetch server-evaluated variant assignments alongside plain booleans. isFeatureOn and evaluateFeatureGate keep working exactly as before — they depend on enabled alone, regardless of any variant assignment. getVariant and getVariantValue are stricter: they return a value only when the feature is enabled and the server has assigned it a variant; either check on its own returns nil.

Toggly.configure(config: TogglyConfig(
appKey: "your-app-key",
enableVariants: true
))
await Toggly.shared.initialize()

if let variant = await Toggly.shared.getVariant("checkout-experiment") {
switch variant.name {
case "Treatment": showTreatment(config: variant.configurationValue)
default: showControl()
}
}

// Or fetch only the payload:
let config = await Toggly.shared.getVariantValue("checkout-experiment")

getVariant returns nil when the feature is off or has no variant assigned. configurationValue is an untyped JSON payload (Any?) — cast it to the shape your dashboard configuration uses. In SwiftUI, use the @FeatureVariant property wrapper from TogglySwiftUI for reactive access.

Configuration Options​

TogglyConfig(
appKey: "your-app-key", // Your Toggly.io app key
environment: "Production", // Environment name
baseURI: "https://definitions.toggly.io", // API endpoint (optional)
identity: "user-123", // User ID for targeting (optional)
featureDefaults: [ // Default values (optional)
"feature-a": true,
"feature-b": false
],
showFeatureDuringEvaluation: false, // Show content while loading
refreshInterval: 180, // Auto-refresh interval (seconds)
useSignedDefinitions: false, // Use signed definitions
storage: UserDefaultsStorage() // Custom storage (optional)
)

Offline Support​

The SDK automatically caches feature flags for offline use:

// Use UserDefaults for persistent storage
let config = TogglyConfig(
appKey: "your-app-key",
environment: "Production",
storage: UserDefaultsStorage()
)

Local-Only Mode​

Use the SDK without connecting to Toggly.io:

let config = TogglyConfig(
featureDefaults: [
"new-dashboard": true,
"beta-feature": false
]
)
Toggly.configure(config: config)
await Toggly.shared.initialize()

Entity context​

Pass the screen entity on each evaluation — not via setIdentity. See Entity & page context.

iOS is a client: local registerContext only. No PUT sdk/{appKey}/contexts.

Toggly.shared.registerContext("Order") { value in
let order = value as! Order // This mapper is called with your Order model.
return TogglyEntityContext(
kind: "Order", key: String(order.id),
attributes: ["Status": order.status]
)
}

let on = await Toggly.shared.isEnabled("OrderBadge", context: order, kind: "Order")

Gates fail closed without context. Identity is user only.

Next Steps​