Skip to main content

SwiftUI Integration

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

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

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
}