Conditional Bindings in SwiftUI

Perhaps you have a component that has a Binding to a state. So that component, when given a binding, can alter the state, but does not own that state; it just has a Binding to it.

It does not own that state because the owner of that state might have even more plans for that state variable, given the context it finds itself in.

So there might be times where the component that is Binding that state should not change the state. A very specific use case is a segmented control. Perhaps you tap to change the segment of the segmented control, but due to some other state, like the data to display is not yet available, you might want to disallow that.

I present: the conditional Binding, and it looks like this:

extension Binding
{
    /// A means to introduce conditional logic into a binding as to whether it should change or not.
    func onChange(
        willSet willSetHandler: ((Value, Value) -> Bool)?,
        didSet didSetHandler: ((Value, Value) -> Void)?
    ) -> Binding<Value> {

        Binding(
            get: { self.wrappedValue },
            set: { newValue in
                let oldValue = self.wrappedValue
                let shouldChange = willSetHandler?(oldValue, newValue) ?? true

                if shouldChange {
                    self.wrappedValue = newValue
                    didSetHandler?(oldValue, newValue)
                }
            }
        )
    }
}

You can see above that this enables a few things:

  • You have callbacks for willSet and didSet, which would not work for a @State var
  • You can, via the return value in the willSetHandler if the value should change at all.

Then in your view builder method, you would have something like:

MySelectionControl(
         selectedIndex: $selectedIndex.onChange(
                willSet: { 
                     /* Stuff that determines whether to return true or false */ 
                           return true   // or false!
                 }, 
                 didSet: nil
         )
 )

Handy little trick I'd say!