I've got another issue with Gesture.updating(_:body:).. This looks a bit like this thread, but it's different. My problem occurs when creating a simultaneous gesture combining a RotateGesture with a MagnifyGesture.
struct ManipulationState: Equatable {
var magnification: CGFloat?
var rotation: Angle?
}
@GestureState var state: ManipulationState? = nil
var gesture: GestureStateGesture<SimultaneousGesture<RotateGesture, MagnifyGesture>, ManipulationState?> {
SimultaneousGesture(
RotateGesture(minimumAngleDelta: .degrees(5)),
MagnifyGesture(minimumScaleDelta: 0.1)
)
.updating($state) { value, state, transaction in
state = ManipulationState(
magnification: value.second?.magnification,
rotation: value.first?.rotation
)
}
}
When rotating and magnifying quite a bit, at some point, something happens, the gesture stops working altogether and the state never resets to the initial value any more.
When adding an onChange handler to some view in the body, you can watch state never gets nil
again.
.onChange(of: state, { oldValue, newValue in
print("\(oldValue) \(newValue)")
})
I noticed the same happening when using ExclusiveGesture
instead of SimultaneousGesture
.
Full code example here: https://github.com/teameh/GestureIssueTester/blob/main/GestureTester/ContentView.swift
Video demonstrating issue here: https://github.com/teameh/GestureIssueTester/raw/refs/heads/main/screen-recording.mov
I've tried using Gesture.onChanged(_:)
and Gesture.onEnded(_:)as well, but
onEnded` is also not always called.
Is there anyone here who ran into the same problem or perhaps someone can share tips on a workaround or a fix?
Tested using Xcode 16.0 (16A242d)
Since GestureState
resets its property back to its initial state when the gesture ends, you should provide an initial state instead of nil
.
Also Gesture.onChanged(_:)
and Gesture.onEnded(_:)
introduces a new type which conflicts with the type specified : var gesture: GestureStateGesture<SimultaneousGesture<RotateGesture, MagnifyGesture>, ManipulationState?>
You should try this instead:
struct Model: Equatable {
var rotation: Angle = .zero
var magnification: CGFloat = 1.0
}
struct ContentView: View {
@GestureState private var gestureState = Model()
var simultaneousGesture: some Gesture {
SimultaneousGesture(
RotateGesture(minimumAngleDelta: .degrees(5)),
MagnifyGesture(minimumScaleDelta: 0.1)
)
.updating($gestureState) { value, state, _ in
if let rotation = value.first {
state.rotation = rotation.rotation
}
if let magnification = value.second {
state.magnification = magnification.magnification
}
}
.onChanged { value in
if let rotation = value.first?.rotation {
print("Rotation onChange: \(rotation.degrees) degrees")
}
if let magnification = value.second?.magnification {
print("Magnification onChange: \(magnification)")
}
}
.onEnded { value in
if let rotation = value.first?.rotation {
print("Rotation ended at: \(rotation.degrees) degrees")
}
if let magnification = value.second?.magnification {
print("Magnification ended at: \(magnification)")
}
}
}
var body: some View {
Rectangle()
.fill(Color.orange)
.frame(width: 200, height: 200)
.shadow(radius: 10)
.scaleEffect(gestureState.magnification)
.rotationEffect(gestureState.rotation)
.gesture(simultaneousGesture)
.onChange(of: gestureState) {
print(gestureState)
}
}
}