I would like an analog clock, I could set the hands (rectangle) of the clock manually, using DragGesture control. But if I want to set the hand backwards immediately after clicking, it turns 180 degrees. If I want to move it forward, it works fine, but not backwards. What could be the problem?
struct ContentView: View {
@State private var angle: Double = 0
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 10, height: 100)
.rotationEffect(Angle(degrees: angle), anchor: .bottom)
.gesture(
DragGesture()
.onChanged { value in
let vector = CGVector(dx: value.translation.width, dy: value.translation.height)
let radians = atan2(vector.dy, vector.dx)
let newAngle = radians * 180 / .pi
self.angle = Double(newAngle)
}
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
OK, I was puzzled by this issue and reevaluated it from scratch. And conclusion is quite different !
The problem was that the angle you compute was not the rotation angle of the rect (the hand).
I illustrate by a figure below.
The following code finally solves all issues (it also allows to chain multiple gestures):
struct ContentView: View {
@State private var angle: Double = 0
@State var currentVector = CGVector.zero
@State var lastVector = CGVector.zero // Keep track where we ended
let handHeight = CGFloat(100) // In case value changed later
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 10, height: handHeight)
.rotationEffect(Angle(degrees: angle), anchor: .bottom)
.gesture(
DragGesture()
.onChanged { value in
currentVector = CGVector(dx: value.translation.width + lastVector.dx, dy: value.translation.height + lastVector.dy)
let radians = atan2(currentVector.dy - handHeight, currentVector.dx)
let newAngle = 90 + radians * 180 / .pi // That is the new rotation angle for the hand
self.angle = Double(newAngle)
}
.onEnded { value in // Needed to start a new Gesture
self.lastVector = currentVector
print(self.lastVector)
}
)
}
}
Geometrical explanation: