Skip to content

Feature Flags (SwiftUI)

A SwiftUI guide for coding agents. Also covers feature flag environment, build configuration flags, testflight vs appstore builds, compilation conditions ios, trunk based development ios, toggle unfinished features.

Compile-time feature flags keyed off the build configuration, shared through the SwiftUI environment. They support trunk-based development: merge unfinished features to main, ship them dark in the App Store build, and light them up in Debug and TestFlight.

Map configurations to a Distribution enum

Duplicate the Release configuration into AppStore and TestFlight, give each its own scheme and compilation condition, then resolve the active one once.

public enum Distribution: Sendable {
  case debug
  case appstore
  case testflight
}

extension Distribution {
  static var current: Self {
    #if APPSTORE
    return .appstore
    #elseif TESTFLIGHT
    return .testflight
    #else
    return .debug
    #endif
  }
}

Derive the flags per distribution

One struct owns every flag, initialized by switching on the distribution. Debug turns everything on. TestFlight disables the paywall so testers can exercise paid features. AppStore keeps unfinished work off.

public struct FeatureFlags: Sendable, Decodable {
  public let requirePaywall: Bool
  public let requireOnboarding: Bool
  public let featureX: Bool

  public init(distribution: Distribution) {
    switch distribution {
    case .debug:
      (requirePaywall, requireOnboarding, featureX) = (true, true, true)
    case .appstore:
      (requirePaywall, requireOnboarding, featureX) = (true, true, false)
    case .testflight:
      (requirePaywall, requireOnboarding, featureX) = (false, true, true)
    }
  }
}

Share through the environment

Define an environment entry with the @Entry macro and inject the current flags at the root, so any view can gate its behavior.

extension EnvironmentValues {
  @Entry public var featureFlags = FeatureFlags(distribution: .debug)
}

@main
struct CardioBotApp: App {
  var body: some Scene {
    WindowGroup {
      RootView()
        .environment(\.featureFlags, FeatureFlags(distribution: .current))
    }
  }
}

Flags are temporary: delete each one once the feature has shipped and proven itself. If the project grows, move the values behind remote configuration so rollout and rollback are instant.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-feature-flags-in-swift" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems