Skip to main content

SwiftUI Integration

The TogglySwiftUI package provides SwiftUI-native components for feature flag management.

Telemetry and session ownership​

Use the existing Core service for frontend telemetry and session token rotation. Telemetry is on by default with an app key; configure enableTelemetry: false to opt out. metricsBaseUrl and telemetryFlushIntervalMs are independent of definition refresh settings. UI evaluation records checks; record usage and views explicitly from the real interaction or navigation callback. Forward background lifecycle to the owning service and dispose directly owned services at teardown. Storage and UI companions reuse the owner; they do not create additional reporters.

Installation​

Add TogglySwiftUI to your target:

.product(name: "TogglySwiftUI", package: "Toggly.FeatureManagement")

Property Wrappers​

@FeatureFlag​

The @FeatureFlag property wrapper provides reactive access to a single feature flag.

import TogglySwiftUI

struct MyView: View {
@FeatureFlag("new-feature") var isNewFeatureEnabled
@FeatureFlag("beta-mode", defaultValue: true) var isBetaMode

var body: some View {
VStack {
if isNewFeatureEnabled {
NewFeatureView()
}

if isBetaMode {
BetaBadge()
}
}
}
}

The view automatically re-renders when the feature flag changes.

@FeatureGate​

The @FeatureGate property wrapper evaluates multiple feature flags with AND/OR logic.

import TogglySwiftUI

struct PremiumView: View {
// All features must be enabled
@FeatureGate(["premium-tier", "new-features"], requirement: .all)
var hasPremiumFeatures

// At least one feature must be enabled
@FeatureGate(["discount-banner", "promo-code"], requirement: .any)
var showPromotion

var body: some View {
VStack {
if hasPremiumFeatures {
PremiumContent()
}

if showPromotion {
PromotionBanner()
}
}
}
}

Negation​

Use the negate parameter to invert the result:

// Show when feature is OFF
@FeatureGate(["maintenance-mode"], negate: true) var isNotInMaintenance

@FeatureVariant​

The @FeatureVariant property wrapper provides reactive access to a server-evaluated variant assignment. Requires enableVariants: true on TogglyConfig.

import TogglySwiftUI

struct CheckoutView: View {
@FeatureVariant("checkout-experiment") var variant

var body: some View {
switch variant?.name {
case "Treatment":
TreatmentCheckout()
default:
ControlCheckout()
}
}
}

variant is nil when the feature is off or has no variant assigned. Use variant?.configurationValue for the untyped payload attached to the assignment.

View Modifiers​

.featureFlag()​

Show a view only when a feature is enabled:

struct ContentView: View {
var body: some View {
VStack {
// Only visible when "new-header" is enabled
HeaderView()
.featureFlag("new-header")

MainContent()

// Only visible when "new-footer" is enabled
FooterView()
.featureFlag("new-footer")
}
}
}

.featureFlag(..., negate: true)​

Show a view only when a feature is disabled:

struct ContentView: View {
var body: some View {
VStack {
// Legacy banner shown when "new-banner" is OFF
LegacyBanner()
.featureFlag("new-banner", negate: true)

// New banner shown when "new-banner" is ON
NewBanner()
.featureFlag("new-banner")
}
}
}

Paired .featureFlag() modifiers​

Prefer separate modifiers (or FeatureView + negate) for on and off paths:

struct ContentView: View {
var body: some View {
NewCheckoutFlow()
.featureFlag("new-checkout")

LegacyCheckoutFlow()
.featureFlag("new-checkout", negate: true)
}
}
FeatureView("new-checkout") {
NewCheckoutFlow()
}
FeatureView("new-checkout", negate: true) {
LegacyCheckoutFlow()
}

FeatureView​

The FeatureView component provides declarative conditional rendering. Set negate: true to render when the feature is off — the same idea as .NET <feature negate>.

Basic Usage​

import TogglySwiftUI

struct ContentView: View {
var body: some View {
VStack {
// Only renders when enabled
FeatureView("welcome-banner") {
WelcomeBanner()
}

// On and off paths
FeatureView("dark-mode") {
DarkModeSettings()
}
FeatureView("dark-mode", negate: true) {
Text("Dark mode coming soon!")
}
}
}
}

FeatureGateView​

For multiple feature flag evaluation:

struct ContentView: View {
var body: some View {
// All features must be enabled
FeatureGateView(["premium", "new-ui"], requirement: .all) {
PremiumNewUI()
}
FeatureGateView(["premium", "new-ui"], requirement: .all, negate: true) {
StandardUI()
}

// Any feature must be enabled
FeatureGateView(["promo-a", "promo-b"], requirement: .any) {
PromotionBanner()
}
}
}

Environment Integration​

TogglyProvider​

Wrap your app with TogglyProvider to make the service available throughout the view hierarchy:

import TogglySwiftUI

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

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

Custom Service Instance​

Pass a specific service instance using the environment:

struct ContentView: View {
let customService = TogglyService(config: TogglyConfig(
featureDefaults: ["test-feature": true]
))

var body: some View {
ChildView()
.togglyService(customService)
}
}

Accessing the Service​

Access the Toggly service from the environment:

struct MyView: View {
@Environment(\.togglyService) var togglyService

var body: some View {
Button("Refresh") {
Task {
await togglyService?.refresh()
}
}
}
}

Using with Specific Service​

All property wrappers and views accept an optional service parameter:

struct TestView: View {
let testService: TogglyService

// Use specific service instead of shared instance
@FeatureFlag("test-feature", service: testService) var isEnabled

var body: some View {
FeatureView("another-feature", service: testService) {
TestContent()
}
}
}

Entity context​

Pass the entity per view, not on setIdentity. See Entity & page context. Clients do not register schemas.

FeatureView("OrderBadge", context: order, contextKind: "Order") {
BadgeView()
}

Missing entity context fails closed.

Best Practices​

1. Initialize Early​

Initialize Toggly in your app's init() or AppDelegate:

@main
struct MyApp: App {
init() {
Toggly.configure(config: TogglyConfig(appKey: "your-key"))
Task { await Toggly.shared.initialize() }
}
}

2. Use Feature Defaults​

Provide defaults for a smooth offline experience:

TogglyConfig(
appKey: "your-key",
featureDefaults: [
"critical-feature": true,
"experimental-feature": false
]
)

3. Handle Loading States​

Use showFeatureDuringEvaluation to control loading behavior:

TogglyConfig(
appKey: "your-key",
showFeatureDuringEvaluation: false // Hide content until loaded
)

4. Organize Feature Keys​

Use constants for feature keys:

enum Features {
static let newDashboard = "new-dashboard"
static let darkMode = "dark-mode"
static let betaFeatures = "beta-features"
}

struct MyView: View {
@FeatureFlag(Features.newDashboard) var isNewDashboard
}