swiftuiviewrotationdraggesture

Rotation of view goes backwards with drag gesture


I have a view that I want to be able to drag around - kind of like a non-free spinning wheel of fortune. The dragging works fine on the y axis and at the bottom of the wheel, but with the x - the view rotates in the wrong direction when dragging the top of the wheel.

I am pretty sure this just requires a little extra logic - but everything I have tried doesn't work - such as checking to see if v.startLocation.x < v.location.x.

import SwiftUI

struct SpinningSelectorWheel: View {

var numberOfSegments: Int = 5
private var colorArray:[Color] = [.red, .green, .blue, .yellow, .brown, .gray, .purple, .red, .green, .blue, .yellow, .brown, .gray, .purple]
@State private var angle: CGFloat = 0
@State private var lastAngle: CGFloat = 0
@State private var length : CGFloat = 400
@State private var gtheta: CGFloat = 0
@State private var gstartx: CGFloat = 0
@State private var gstarty: CGFloat = 0

var body: some View {
    GeometryReader
    { fullsize in
        //Text("\(angle)")
        VStack
        {
            Text("\(gtheta)")
            Text("\(gstartx)")
            Text("\(gstarty)")
            Text("\(length)")
        }
        ForEach((1...numberOfSegments).reversed(), id: \.self)
        {segment in
            Path { path in
                path.move(to: CGPoint(x: fullsize.size.width/2, y: fullsize.size.height/2))
                path.addArc(center: .init(x: fullsize.size.width/2, y: fullsize.size.height/2), radius: 150, startAngle: Angle(degrees: Double(1-segment)  * Double(360/numberOfSegments)), endAngle: Angle(degrees: Double(360/numberOfSegments)), clockwise: false)
            }
            .fill(colorArray[segment-1])
        }
        .rotationEffect(.degrees(Double(self.angle))).gesture(DragGesture().onChanged
            { v in
            var theta = (atan2(v.location.x - self.length / 2, self.length / 2 - v.location.y) - atan2(v.startLocation.x - self.length / 2, self.length / 2 - v.startLocation.y)) * 180 / .pi
            gtheta = theta
            gstartx = v.startLocation.x
            gstarty = v.startLocation.y
            if (theta < 0) { theta += 360}
            self.angle = theta + self.lastAngle
            }
            .onEnded { v in
                self.lastAngle = self.angle
            }
        )
    }
}

}


Solution

  • Something I wrote for UIKit that may be adapted to SwiftUI :

    // rotation from point lPrevPoint to lTouchPoint
            let lDeltaDirectionX = Double((lTouchPoint.x - lPrevPoint.x) / 2.0)
            let lDeltaDirectionY = Double((lTouchPoint.y - lPrevPoint.y) / 2.0)
    // only do 1 direction at a time ( the one with biggest change)
    // then you check in which part of the screen you are :
            if abs(lDeltaDirectionX) > abs(lDeltaDirectionY) {
                if lPrevPoint.y > self.frame.size.height / 2.0 {
                    orientation = fmod(orientation-lDeltaDirectionX + 360.0, 360.0)
                } else {
                    orientation = fmod(orientation+lDeltaDirectionX + 360.0, 360.0)
                }
            }
            else
            {
                if lPrevPoint.x > self.frame.size.width / 2.0 {
                    orientation = fmod(orientation+lDeltaDirectionY + 360.0, 360.0)
                } else {
                    orientation = fmod(orientation-lDeltaDirectionY + 360.0, 360.0)
                }
            }
    

    orientation is the angle of rotation . Not perfect because it does not take into account the distance from view center to have a more accurate value. But can be a good start.