SwiftUI Binding Tips
Leverage Dynamic Member Lookup
When working with bindings in SwiftUI, it can be useful to modify bindings. For example, in a Slack I’m part of, someone had a binding to a Boolean and wanted to negate it. This was (roughly) the code they had:
extension Binding where Value == Bool {
func toggled() -> Self {
Self(
get: {
!wrappedValue
},
set: { newValue in
wrappedValue = !newValue
}
)
}
}
While the code works great, it always feels a bit cumbersome to write. There’s a simpler way to achieve this. We can start by creating a computed property on Bool
that’s both get
and set
:
extension Bool {
var flipped: Self {
get { !self }
set { self = !newValue }
}
}
Because Binding
supports dynamic member lookup, we can use our flipped
property directly where we use our binding:
$myBinding.flipped
We’ve also used this pattern sometimes when projecting into a binding. For example, let’s imagine we want to display a control that enables us to select edges. Our state looks like this:
@State private var edges: Set<Edge> = [Edge.top, Edge.bottom]
If we want to enable and disable a specific edge through a Toggle
, we’ll need to turn the binding from a Set<Edge>
into a Bool
binding. We can again achieve this using dynamic member lookup, but this time through a subscript:
extension Set {
subscript(contains el: Element) -> Bool {
get { contains(el) }
set {
if newValue {
insert(el)
} else {
remove(el)
}
}
}
}
Now we can write $edges[contains: .top]
or $edges[contains: .bottom]
and get a Bool
binding out. The reason why we need a subscript
is that dynamic member lookup uses key paths, and both subscripts and computed properties can be part of key paths. A regular method cannot be part of a key path. While the technique is a little obscure, I still find it easier to read than writing all that logic inside a custom binding.
In general, I much prefer components that take a Binding
over components that observe all kinds of complicated view models, global stores or other things. They are easier to test and reuse and don’t require complicated architectures. Hopefully the two tricks above help you turn the bindings that you have into the bindings that you need.
Update : Stephen Celis writes in:
Another reason to prefer properties + dynamic member look up instead of constructing ad-hoc bindings is that ad-hoc bindings tend to break SwiftUI animation. Even if you do all the right stuff with the transaction, there is something weird deep in SwiftUI that causes an ad-hoc binding to be handed transactions with all animation info stripped away.