49

I have a Binding<Bool> binding in a swiftUI view. Something along the lines of:

struct MyCoolView: View { 
    @ObservedObject var viewModel: ViewModel

    var body: some View { 
        Text("Here is a cool Text!").sheet(isPresented: $viewModel.MyProperty) { 
            SomeModalView()}
        }
} 

I want the isPresented to use the opposite boolean value of what is stored in the property using Boolean negation, the exclamation point ! operator, or some other method.

Swift wont let me just do something like

.sheet(isPresented: !$viewModel.MyProperty) 

!$viewModel gives the error:

Cannot convert value of type 'Bool' to expected argument type 'Binding<Bool>'

Any thoughts on how to deal with this?

pkamb
  • 33,281
  • 23
  • 160
  • 191
snarik
  • 1,035
  • 2
  • 9
  • 15
  • 1
    Could you just not invert it already in a the viewModel before it get sent to the view? Or am I missing something? – Aecasorg Apr 30 '21 at 08:47
  • 2
    I should update and say that with about a year and a half of hindsight and a better sense of how to properly use Combine and publishers what you suggest is indeed the correct way to do it. The way I'd approach this now is to create a subscription to the Boolean inside the view model that is stored inside another published variable. I suppose time makes fools of us all on SO. – snarik May 20 '21 at 18:48

4 Answers4

57

What about creating a custom prefix operator?

prefix func ! (value: Binding<Bool>) -> Binding<Bool> {
    Binding<Bool>(
        get: { !value.wrappedValue },
        set: { value.wrappedValue = !$0 }
    )
}

Then you could run your code without any modification:

.sheet(isPresented: !$viewModel.MyProperty) 

If you don't like operators you could create an extension on Binding type:

extension Binding where Value == Bool {
    var not: Binding<Value> {
        Binding<Value>(
            get: { !self.wrappedValue },
            set: { self.wrappedValue = !$0 }
        )
    }
}

and later do something like:

.sheet(isPresented: $viewModel.MyProperty.not)

or even experiment with a global not function:

func not(_ value: Binding<Bool>) -> Binding<Bool> {
    Binding<Bool>(
        get: { !value.wrappedValue },
        set: { value.wrappedValue = !$0 }
    )
}

and use it like that:

.sheet(isPresented: not($viewModel.MyProperty))
tgebarowski
  • 1,189
  • 11
  • 14
  • You mean `b in self.wrappedValue = !b`, right? Other than that nice answer :) – Dávid Pásztor May 22 '20 at 14:08
  • 1
    Actually, when I negate in setter and getter I will have the same result (without negation). So we should either negate in getter or setter not in two places. Probably negating in the setter is more straightforward though. – tgebarowski May 22 '20 at 15:08
  • Nope, your code actually produces flipped results when you use the setter. If you input `true` to the setter, it will set the `value.wrappedValue` to `true` instead of setting the value of the negated binding to `true`. Try setting `returnedBinding.wrappedValue = true` where `returnedBinding` is the output of your `!anotherBinding` operator and you'll see that if you call `returnedBinding.wrappedValue` straight after, it will return `false` instead of `true`. – Dávid Pásztor May 22 '20 at 15:38
  • @tgebarowski very nice extensions and functions, but need a little fix. You should reverse the value on set, like `set: { value.wrappedValue = !$0 }` – alemorgado Aug 26 '20 at 12:38
  • extension is perfect – Lukasz D Oct 30 '21 at 19:27
  • The extension is such an elegant solution, thank you so much! – Michel Storms Jan 04 '23 at 09:35
  • @tgebarowski go wrong if put prefix operate into swift package, see https://stackoverflow.com/questions/75808263/problem-using-prefix-operator-from-framework – foolbear Mar 22 '23 at 13:43
34

You can build a binding by yourself:

Text("Here is a cool Text!").sheet(isPresented:
         Binding<Bool>(get: {return !self.viewModel.MyProperty},
                       set: { p in self.viewModel.MyProperty = p})
          { SomeModalView()} } 
E.Coms
  • 11,065
  • 2
  • 23
  • 35
9

Based off @E.Com's answer, here is a shorter way to construct a Binding<Bool>:

Binding<Bool>(
    get: { !yourBindingBool },
    set: { yourBindingBool = !$0 }
)
Cloud
  • 938
  • 1
  • 8
  • 24
4

Add extension like so:

extension Binding where Value == Bool {
    func negate() -> Bool {
        return !(self.wrappedValue)
    }
}
Mihkel L.
  • 1,543
  • 1
  • 27
  • 42