How do you change a SwiftUI binding to a boolean to give the inverted value?
Suppressing A Confirmation Dialog
Here’s my situation. When a user deletes items, I present a SwiftUI confirmation dialog before proceeding with the operation. A confirmation dialog has a few extra options on macOS compared to iOS. For example, you can include a checkbox to allow the user to opt-out of getting future confirmations:
.confirmationDialog("Delete all items?", isPresented: $isConfirmingDelete) {
Button("Delete", role: .destructive) { ... }
}
.dialogSuppressionToggle(isSuppressed: $isSuppressed)
The .dialogSuppressionToggle
view modifier expects a binding to a boolean which when true
should suppress presenting the confirmation dialog. Unfortunately, the way I’m storing the user’s preference in my app’s settings is using the reverse logic. When the setting is true I want to show the confirmation:
@Observable final class AppSettings {
var confirmDelete = true
}
This seems like a common situation with SwiftUI. I need a binding to a transformed version of the binding I have. What are my options?
Manually Create The Binding
I can provide my own binding with a setter and getter that returns the inverted value I need:
.dialogSuppressionToggle(isSuppressed:
Binding<Bool>(
get: { !appSettings.confirmDelete },
set: { appSettings.confirmDelete = !$0 }
)
)
That works, but is verbose, and can quickly becomes repetitive. I also have to look up the right syntax each time I need to do it.
Extending On Binding
A search on Stack Overflow shows a common approach with an extension on a boolean binding that provides the inverted value:
extension Binding where Value == Bool {
var inverted: Binding<Value> {
Binding<Value>(
get: { !wrappedValue },
set: { wrappedValue = !$0 }
)
}
}
.dialogSuppressionToggle(
isSuppressed: $appSettings.confirmDelete.inverted)
That removes the repetitive boilerplate but left me wondering if I was missing a trick?
Extending Bool
Not really a trick, but I think I prefer the approach suggested by Chris Eidhof (see SwiftUI Binding Tips). Instead of extending Binding he extends Bool
to add a property that returns the inverted value:
extension Bool {
var inverted: Self {
get { !self }
set { self = !newValue }
}
}
Accessing this new property as part of the binding works as before:
.dialogSuppressionToggle(
isSuppressed: $appSettings.confirmDelete.inverted)
Let me know what you think?