Skip to main content

UIKit Support

The TogglyUIKit package provides UIKit-specific utilities and extensions for feature flag management.

Installation​

Add TogglyUIKit to your target:

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

FeatureFlagViewController​

A base view controller that provides feature flag observation and automatic UI updates.

Basic Usage​

import TogglyUIKit

class MyViewController: FeatureFlagViewController {
@IBOutlet weak var newFeatureView: UIView!
@IBOutlet weak var legacyFeatureView: UIView!

override func viewDidLoad() {
super.viewDidLoad()

// Start observing features
observeFeature("new-dashboard")
observeFeature("beta-mode")
}

override func featureFlagDidChange(_ key: String, isEnabled: Bool) {
switch key {
case "new-dashboard":
newFeatureView.isHidden = !isEnabled
legacyFeatureView.isHidden = isEnabled
case "beta-mode":
configureBetaMode(isEnabled)
default:
break
}
}

private func configureBetaMode(_ enabled: Bool) {
// Update UI for beta mode
}
}

Check Current State​

class MyViewController: FeatureFlagViewController {
func updateUI() {
// Check current feature state
if isFeatureEnabled("premium-tier") {
showPremiumContent()
}
}
}

Stop Observing​

class MyViewController: FeatureFlagViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopObservingFeature("temporary-feature")
}
}

Custom Service​

class MyViewController: FeatureFlagViewController {
override func viewDidLoad() {
super.viewDidLoad()

// Use a custom Toggly service instance
togglyService = customTogglyService

observeFeature("my-feature")
}
}

UIView Extensions​

Visibility Binding​

Bind a view's visibility to a feature flag:

import TogglyUIKit

class MyViewController: UIViewController {
@IBOutlet weak var newBannerView: UIView!
@IBOutlet weak var legacyBannerView: UIView!

override func viewDidLoad() {
super.viewDidLoad()

// Show when feature is enabled
newBannerView.bindToFeatureFlag("new-banner")

// Show when feature is disabled
legacyBannerView.bindToFeatureFlag("new-banner", hideWhenEnabled: true)
}
}

Unbind​

Remove the feature flag binding:

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
newBannerView.unbindFromFeatureFlag()
}

UIControl Extensions​

Enabled State Binding​

Bind a control's enabled state to a feature flag:

import TogglyUIKit

class MyViewController: UIViewController {
@IBOutlet weak var premiumButton: UIButton!
@IBOutlet weak var legacyButton: UIButton!

override func viewDidLoad() {
super.viewDidLoad()

// Enable when feature is on
premiumButton.bindEnabledToFeatureFlag("premium-features")

// Disable when feature is on (enable when off)
legacyButton.bindEnabledToFeatureFlag("new-mode", disableWhenEnabled: true)
}
}

Async/Await Utilities​

The FeatureFlagAsync enum provides async/await helpers for checking feature flags.

Basic Checks​

import TogglyUIKit

class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

Task {
// Check if feature is enabled
let isEnabled = await FeatureFlagAsync.isEnabled("my-feature")

// Check if feature is disabled
let isDisabled = await FeatureFlagAsync.isDisabled("my-feature")

await MainActor.run {
updateUI(featureEnabled: isEnabled)
}
}
}
}

Feature Gate Evaluation​

Task {
// All features must be enabled
let allEnabled = await FeatureFlagAsync.evaluate(
["feature-a", "feature-b"],
requirement: .all
)

// Any feature must be enabled
let anyEnabled = await FeatureFlagAsync.evaluate(
["promo-a", "promo-b"],
requirement: .any
)

// Negated result
let notInMaintenance = await FeatureFlagAsync.evaluate(
["maintenance-mode"],
negate: true
)
}

Conditional Execution​

Task {
// Execute only if feature is enabled
await FeatureFlagAsync.ifEnabled("analytics") {
trackScreenView()
}

// Execute only if feature is disabled
await FeatureFlagAsync.ifDisabled("new-api") {
useLegacyAPI()
}

// Choose between two actions
let result = await FeatureFlagAsync.choose(
"new-checkout",
enabled: { await processNewCheckout() },
disabled: { await processLegacyCheckout() }
)
}

Custom Service​

All async methods accept an optional service parameter:

let customService = TogglyService(config: TogglyConfig(...))

Task {
let isEnabled = await FeatureFlagAsync.isEnabled(
"my-feature",
service: customService
)
}

Programmatic Usage​

Direct Service Access​

import TogglyCore

class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

Task {
// Check feature flags
let isEnabled = await Toggly.shared.isFeatureOn("my-feature")

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

await MainActor.run {
configureUI(featureEnabled: isEnabled, allFeaturesEnabled: allEnabled)
}
}
}
}

Listen for Changes​

class MyViewController: UIViewController {
private var unsubscribe: (() -> Void)?

override func viewDidLoad() {
super.viewDidLoad()

Task {
unsubscribe = await Toggly.shared.addStateChangeHandler { [weak self] key, previous, new in
guard key == "my-feature" else { return }
Task { @MainActor in
self?.handleFeatureChange(isEnabled: new ?? false)
}
}
}
}

deinit {
unsubscribe?()
}
}

Entity context​

Pass the entity on each UIKit check — not on setIdentity. See Entity & page context. Clients do not register schemas.

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

Gates fail closed without context.

Best Practices​

1. Use FeatureFlagViewController​

Subclass FeatureFlagViewController for cleaner code:

// Good: Use the base class
class MyVC: FeatureFlagViewController {
override func featureFlagDidChange(_ key: String, isEnabled: Bool) {
// Handle changes
}
}

// Avoid: Manual subscription management
class MyVC: UIViewController {
var unsubscribes: [() -> Void] = []
// More boilerplate...
}

2. Handle Main Thread​

UI updates must be on the main thread:

Task {
let isEnabled = await FeatureFlagAsync.isEnabled("feature")

await MainActor.run {
self.updateUI(enabled: isEnabled)
}
}

3. Clean Up Bindings​

Remove bindings when views are removed:

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
myView.unbindFromFeatureFlag()
}

4. Use Feature Constants​

Define feature keys as constants:

enum Features {
static let newDashboard = "new-dashboard"
static let premiumMode = "premium-mode"
}

observeFeature(Features.newDashboard)