Is there any way to create a new Gesture in SwiftUI?

984 views Asked by At

SwiftUI is missing a Pan gesture (that is, both scale and offset), so I was trying to create one. However, it appears that the Gesture struct depends on private classes. For example:

public struct PinchGesture: Gesture {

    public struct PinchGestureValue: Equatable {
        var scale: CGFloat
        var anchor: UnitPoint
        var offset: CGSize
        var isPinching: Bool
    }
    
    public typealias Value = PinchGestureValue
    public typealias Body = Never
    
    var minimumScaleDelta: CGFloat
    var minimumDistance: CGFloat
    var coordinateSpace: CoordinateSpace
    
    
    public init(minimumScaleDelta: CGFloat = 0.01, minimumDistance: CGFloat = 10, coordinateSpace: CoordinateSpace = .local) {
        self.minimumScaleDelta = minimumScaleDelta
        self.minimumDistance = minimumDistance
        self.coordinateSpace = coordinateSpace
    }
    
    public static func _makeGesture(gesture: _GraphValue<PinchGesture>, inputs: _GestureInputs) -> _GestureOutputs<PinchGestureValue> {
      // Unable to complete
    }

}

This code cannot be completed, as the _GraphValue, _GestureInputs, and _GestureOutputs are private. Before I give in completely, I wanted to see if anyone has figured out a workaround.

1

There are 1 answers

0
rob mayoff On BEST ANSWER

SwiftUI provides a default implementation of _makeGesture:

extension Gesture where Self.Value == Self.Body.Value {
  public static func _makeGesture(gesture: SwiftUI._GraphValue<Self>, inputs: SwiftUI._GestureInputs) -> SwiftUI._GestureOutputs<Self.Body.Value>
}

The difficulty here is the constraint Self.Value === Self.Body.Value. That means your gesture's body can't be declared to return some Gesture, because some Gesture can't satisfy the constraint (even if its Value would match). So you have to give body a specific type. The easiest solution is to use the AnyGesture type eraser:

public struct PinchGesture: Gesture {

    ...

    public var body: AnyGesture<PinchGestureValue> {
        AnyGesture(
            DragGesture(minimumDistance: 0, coordinateSpace: .global)
               .map { PinchGestureValue($0) }
        )
    }
}

In this code, Swift can infer PinchGesture.Value = PinchGestureValue and PinchGesture.Body = AnyGesture<PinchGestureValue>. Then it can prove AnyGesture<PinchGestureValue>.Value == PinchGesture.Value, so it can use the default implementation of _makeGesture provided by SwiftUI.

Unfortunately, I doubt you can use this to create your PinchGesture. Ultimately, your body is still restricted to combining SwiftUI's primitive gestures, which don't give you access the current UIEvent or UITouch objects.