Calculate angle from touch events

I need a grater range of recognition over the default UISwipeGestureRecognizer and turned to trig to make that happen, but it didn't. My understanding was that if I knew alpha (press down), and beta (release touch), as well as the top of the view being zero, then I could calculate the angle:

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        let touch = touches.first
        let touchLocation = touch?.location(in: self)
        
        touchDown = touchLocation ?? .zero
        print("ANGLE : ORIGIN : \(atan2(touchDown.x, touchDown.y))")
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        let touch = touches.first
        let touchLocation = touch?.location(in: self)
        
        touchUp = touchLocation ?? .zero
        print("ANGLE : FIN : \(atan2(touchUp.x, touchUp.y))")
    
        let theta = atan2((touchDown.x - touchDown.y), (touchUp.x - touchUp.y))
        let deg = theta * 180 / .pi
        
        print("RAD -> DEG : \(deg)")
    }

However, the angles do not seem to be coming through as I had hoped.

I didn't look closely but this looks wrong, should be:

let theta = atan2((touchUp.y - touchDown.y), (touchUp.x - touchDown.x))

The trajectory is increasing linearly now.

        let betaX = (shape.position.x + distance) * cos(deg)  //END = START + DISTANCE x COS
        let betaY = (shape.position.y + distance) * cos(deg)
        shape.position = CGPoint(x: betaX, y: betaY)

The above code was to interpret the two touch states as a swipe (with x amount of time between the two, differentiating it from a tap event), thus allowing for a swipe anywhere on screen, but relatively applying the movement logic to an object on screen.

It still does not work properly.

Why are you converting to degrees? cos/sin work with radians.
I will leave it up to you to check the math (and whether that should be sin for one of the components or not). I would recommend you to recheck the math and if that doesn't help - log the relevant intermediate values into the console to see if they are matching your expectations.

That's why I converted it, so I could check in the lldb.

This question is more suited for StackOverflow (and maybe Apple's dev forums) than Swift forums, so I suggest you go there instead. You might have better luck there.


I think you should try to play around with conversions a little in Swift Playground before diving into fixing the bug, at least to get some handle on the trig. If you have some iPads, this Swift Playground snippet could be useful*. Note that I use touchesMoved instead of touchEnded for a more interactive experience.

import UIKit
import SwiftUI
import PlaygroundSupport

class XView: UIView {
    var updateText: ((LocalizedStringKey) -> ())!
    var touchDown: CGPoint!
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        touchDown = touch!.location(in: self)
        
        updateText("Origin: (\(touchDown.x, specifier: "%.2f"), \(touchDown.y, specifier: "%.2f"))\n\n")
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self)
        
        let touchUp = touchLocation
        let angle = atan2(touchDown.y - touchUp.y, touchDown.x - touchUp.x)
        
        updateText("""
            Origin: (\(touchDown.x, specifier: "%.2f"), \(touchDown.y, specifier: "%.2f"))
            Current: (\(touchUp.x, specifier: "%.2f"), \(touchUp.y, specifier: "%.2f"))
            Angle: \(angle)
            """)
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        touchDown = nil
        updateText("")
    }
}
struct XViewRepresentation: UIViewRepresentable {
    @Binding var text: LocalizedStringKey
    
    func makeUIView(context: Context) -> XView {
        let uiView = XView()
        uiView.updateText = { text = $0 }
        return uiView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {}
}
struct MainView: View {
    @State var text: LocalizedStringKey = ""
    var body: some View {
        ZStack {
            Text(text)
                .monospacedDigit()
                .frame(width: 200, alignment: .leading)
            XViewRepresentation(text: $text)
        }
    }
}

PlaygroundPage.current.setLiveView(MainView())

Now, depending on where you want the 0-radian angle is (and whether it increases clockwise or counterclockwise), the angle formula above could look very different. So it depends on how you intend to use it.

As @tera mentioned cos and sin works with radians, so you're simply using the wrong units regardless of your intention (and things would be spinning about 60x faster than should be). Plus, that code simply moves shape in X=Y direction by distance, then stretches it by a constant cos(deg). Since I don't know your intended transformation, I can't say much. Then again, you may have better luck asking over SO than here.

* On macOS, I'd suggest that you use Xcode's playground instead. The iOS playground on macOS has been a subpar experience for me.

1 Like