SwiftUI Inverting A Boolean Binding

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:

confirmation dialog with unchecked don’t ask again option

.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?