SwiftUI Sheet show/dismiss with onResponse controlled by a feature flag

Tom Zurkan
3 min readFeb 9, 2023

Let’s say you want to display a coupon with a modal dialog that has a button to redeem. In a feature addition like this, you may want to put it behind a feature flag. A feature flag is a way of wrapping a feature so that it can be remotely turned on or off (like show the coupon or not). You can also use feature flags to pass values like string, json, etc. For now, we are only looking at basic on/off feature flag.

So, what I wanted to do was show or hide the sheet by toggling the feature flag. I thought I would run through how you would use a sheet and a feature flag if it were critical that the sheet responded instantly to any changes in feature flag status. You could have easily used a timer or some other programmatic mechanism to trigger the display or dismissal of a sheet. I thought a feature flag would be a good way to demonstrate.

I am going to sudo code the feature flag because the feature flag client you use is irrelevant in this conversation. You can grow your own, or use something like LaunchDarkly, Split, or even Optimizely.

First, you are going to initialize your feature flag client and broadcast any changes.

import SwiftUI


class GlobalListener : FeatureFlagChangeListener {
static let NOTIFICATION_NAME = "FeatureFlagUpdated"

func feautureFlagChange(ffKey:String) {
DispatchQueue.main.async {
NotificationCenter.default.post(name: Notification.Name(GlobalListener.NOTIFICATION_NAME), object: ffKey)
}
}
}

let globalListener = GlobalListener()

@main
struct MyFeatureFlagAppApp: App {

init() {
OptimizelyClient.initialize(sdkKey: "someKey", listener: globalListener) { err in
if let err = err {
logger.error(err.localizedDescription)
}
else {
NotificationCenter.default.post(name: Notification.Name(GlobalListener.NOTIFICATION_NAME), object: nil)
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Ok, so above, we setup our listener and initialized our feature flag client. When we detect a feature flag change, we send out a Notification Center notification. Keep in mind that this technique could be used with other programatic mechanisms such as a timer or something else.

Finally, we have the content view, observable content, and the detail coupon view listed below.

import SwiftUI

struct CouponView: View {
var message: String
@Binding var isPresented: Bool

var body: some View {
VStack {
Text(message)
Button("Redeem Coupon") {
isPresented = false
// let them get the discount
}
}
}
}

class ObservableContent : ObservableObject {
@Published var showingCoupon = false

static let defaultInstance = ObservableContent()
}

struct ContentView: View {
@StateObject private var coupon = ObservableContent.defaultInstance

var id:NSObjectProtocol?

init() {
id = NotificationCenter.default.addObserver(forName: NSNotification.Name(GlobalListener.NOTIFICATION_NAME), object: nil, queue: nil) { notify in
var str = notify.object as? String ?? "keyIExpect"
if str == "keyIExpect" {
OptimizelyFeatureFlagClient.evaluateBooleanVariation(featureFlagKey: str, defaultValue: false, completionHandler: { enabled in
DispatchQueue.main.async {
ObservableContent.defaultInstance.showingCoupon = enabled
}
})
}
}

var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")

}
.padding()
.sheet(isPresented: $coupon.showingCoupon) {
CouponView(message: "Coupon for 100% off", isPresented: $coupon.showingCoupon).onReceive(NotificationCenter.default.publisher(for: Notification.Name(rawValue: GlobalListener.NOTIFICATION_NAME))) { notify in
var str = notify.object as? String ?? "keyIExpect"
if str == "keyIExpect" {
OptimizelyFeatureFlagClient.evaluateBooleanVariation(featureFlagKey: str, defaultValue: false, completionHandler: { enabled in
DispatchQueue.main.async {
ObservableContent.defaultInstance.showingCoupon = enabled
}
})
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

So, what is going on with the code above? First, you have an observable object in your content view as a state object. You then use binding to bind that published item with the coupon view’s isPresented. Then the sheet ends up with isPresented equal to true when the feature flag is on. The coupon detail view uses the views onReceive. We use the notification center default publisher with the notification name. If the flag turns false, the showingCoupon is also false causing CouponView to also set isPresented to false which dismisses the detail view or coupon.

Lastly, you launch your app and turn on your feature flag:

Oh no! It says 100% off on the coupon. Go over and switch off the feature flag and the coupon disappears. Phew!. This is just an over dramatization of why feature flags are important while also showing how to control a sheet that is displayed without user interaction.

I hope this short article helps you in your SwiftUI coding. I think SwiftUI is amazing fun and a massive improvement over UIViewControllers and Xibs.

--

--

Tom Zurkan

Engineer working on full stack. His interests lie in modern languages and how best to develop elegant crash proof code.