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.
iOS 17 introduced MagnifyGesture
, which gives you more details about the magnification gesture in progress. You might be able to combine this with DragGesture
using SimultaneousGesture
to implement the combined pan and scale gesture you want.
iOS 18 introduced UIGestureRecognizerRepresentable
, which lets you use any UIGestureRecognizer
with a SwiftUI View. You should be able to attach both a UIPinchGestureRecognizer
and a UIPanGestureRecognizer
to your SwiftUI view for the combined pan and scale gesture you want.
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.