Conditional gestures in SwiftUI
15 Aug 2020 ⇐ Notes archive(This is an entry in my technical notebook. There will likely be typos, mistakes, or wider logical leaps — the intent here is to “let others look over my shoulder while I figure things out.”)
Conditionally enabling a gesture in SwiftUI wasn’t as intuitive for me compared to other modifiers. Showing or hiding a view is an .opacity(someCondition ? 1 : 0)
(or if
-else
in a ViewBuilder
or .hidden()
) away. But for gestures? It felt off to have to .gesture(someCondition ? DragGesture().onChanged { /* … */ }.onEnded { /* … */ } : DragGesture())
, where the first branch returns a live gesture and the second, an inert one. The types returned from the ternary need to line up — there’s an AnyGesture
eraser in the framework that usually helps in these situations, yet it still begs the question of which instance to erase in the disabled case.
A search online for “SwiftUI disable gesture” tops out with Paul Hudson’s post, “Disabling user interactivity with allowsHitTesting(_:)
” and while that modifier works in some situations, it was too coarse for the one I was in. I needed to disable a drag gesture and keep a tap gesture on the same view in tact and allowsHitTesting
wholesale disables both.
Poking around the Gesture
protocol’s listed conformances had the answer I was looking for — Optional
conditionally conforms to Gesture
!
And Harlan Haskins helped me tidy the above ternary to .gesture(someCondition ? DragGesture().onChanged { /* … */ }.onEnded { /* … */ } : nil)
. What’s wicked here is the gesture is of type _EndedGesture<_ChangedGesture<DragGesture>>
and Swift is able to promote the nil
to an optionally-wrapped version without any added annotations.