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
}