How to use SwiftUI AnyGesture? Why is it not like AnyView?

I couldn't figure out how to use AnyGesture, it not like AnyView!

I'm trying to make a widget that can conditionally handle touch then drag or either long press or touch then drag gesture, just like how the volume/brightness slider work in iOS control center.

So basically I need to do this to my View:

MySlider()
    .gesture(flag ? AnyGesture(gesture1) : AnyGesture(gesture2))    // <----- compile error

Result values in '? :' expression have mismatching types 'SwiftUI.AnyGesture<(some SwiftUI.Gesture).Value>' and 'SwiftUI.AnyGesture<(some SwiftUI.Gesture).Value>'

Anyone know how to use AnyGesture? Why is itself a generic type? Not like AnyView?

AnyGesture has generic parameter Value, which must match for them to be of the same type. This is so that functions that operate on Value like map, onChange would make sense.

Since opaque type doesn't have functionality to specify the generic parameter, yet, you'll need to use something else, like typealias, or simply fully specify the type, or even convert gesture1 and gesture2 itself to return AnyGesture.

// The `Value` must match.
var gesture1: AnyGesture<Value> { ... }
var gesture2: AnyGesture<Value> { ... }

I see what you're saying but don't fully understand, maybe because I don't really understand Swift generic with associatedType. I dun't even know what Value is in AnyGesture as it's not in my gesture code. My gestures:

    func gesture1(geometry: GeometryProxy) -> some Gesture {
        // just tap
        LongPressGesture(minimumDuration: 0)
        .onEnded { _ in
            self.startingValue = self.value
        }
        .sequenced(before: DragGesture(minimumDistance: 0)
            .onChanged {
                let t = self.startingValue - Double(($0.location.y - $0.startLocation.y) / geometry.size.height)
                self.value = min(max(0.0, t), 1.0)
            }
        )
    }

    func gesture2(geometry: GeometryProxy) -> some Gesture {
        // 0.5s long press
        LongPressGesture(maximumDistance: 0)
        .onEnded { _ in self.onLongPress!() }
        .exclusively(before: gesture1(geometry: geometry))
    }

What's is Value here? I can't see it in my code. How to change my code to make it work?

Thanks!

Assuming you actually call gesture1 & gesture2 like this gesture1(geometry: geometry) since they're functions.

As an ad-hoc solution, you can use map to also erase the type of Value.

MySlider()
  .gesture(flag ? AnyGesture(gesture1.map { _ in () }) : AnyGesture(gesture2.map { _ in () }))

Explanation:

Firstly, Gesture has associatedType Value which can usually be inferred from the concrete type that conforms to it. For example, LongPressGesture.Value == Bool.

Now, AnyGesture<Value> is a generic struct with generic parameter Value. So AnyGesture are the same type if-and-only-if Value are the same, for example

  • AnyGesture<Bool> == AnyGesture<Bool>
  • AnyGesture<Bool> != AnyGesture<Int>

Since ternary operator ?: assumes that they're of the same type the equality of Value is also required.

The potential source confusion is that AnyGesture uses Value (generic parameter) to fulfil Gesture.Value (associatedType).

AnyGesture.Value can usually be inferred from context, for example:

AnyGesture(gesture1) can imply AnyGesture<Gesture1.Value> where Gesture1 is the type of gesture1. This is from the init signature

// Initializer of `AnyGesture`
init<T: Gesture>(_ gesture: T)
  where Value == T.Value

which enforces AnyGesture.Value == T.Value.

The problem of using some Gesture as a return value is that, the compiler can not use the actual type of Value from gesture1 and gesture2, much less proof that they're equal.

In this case It's very likely that they're not equal from the sheer structure of the gesture1 and gesture2 function.
So, AnyGesture<Gesture1.Value> != AnyGesture<Gesture2.Value> even if the compiler can infer Value.

So now I map the Value on both of them to Void so that AnyGesture(gesture1.map { _ in () }) is AnyGesture<Void> which matches AnyGesture(gesture2.map { _ in () }).

2 Likes