11

I made a circle Path, the center of the circle path is in the middle of the view. Then, I made a ball that can move only on the circle path (at least this is what I want it to be): enter image description here

I made a function that move the ball wherever I drag him (on the circle path only), but for some reason whenever I drag it, it gets crazy and doesn't move as I want it to move.

This is my code so far:

class ViewController: UIViewController {
    var midViewX = CGFloat()
    var midViewY = CGFloat()

    var circlePath2 = UIBezierPath()
    var shapeLayer2 = CAShapeLayer()
    override func viewDidLoad() {
        super.viewDidLoad()
        midViewX = view.frame.midX
        midViewY = view.frame.midY
        // Do any additional setup after loading the view, typically from a nib.
        let circlePath = UIBezierPath(arcCenter: CGPoint(x: midViewX,y: midViewY), radius: CGFloat(100), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = circlePath.CGPath
        shapeLayer.fillColor = UIColor.clearColor().CGColor
        shapeLayer.strokeColor = UIColor.redColor().CGColor
        shapeLayer.lineWidth = 3.0
        view.layer.addSublayer(shapeLayer)

        var angleEarth: Double = 180
        var angleEarthAfterCalculate: CGFloat = CGFloat(angleEarth*M_PI/180) - CGFloat(M_PI/2)
        var earthX = midViewX + cos(angleEarthAfterCalculate)*100
        var earthY = midViewY + sin(angleEarthAfterCalculate)*100
        circlePath2 = UIBezierPath(arcCenter: CGPoint(x: earthX,y: earthY), radius: CGFloat(10), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
        shapeLayer2.path = circlePath2.CGPath
        shapeLayer2.fillColor = UIColor.blueColor().CGColor
        shapeLayer2.strokeColor = UIColor.clearColor().CGColor
        shapeLayer2.lineWidth = 7
        view.layer.addSublayer(shapeLayer2)

        let dragBall = UIPanGestureRecognizer(target: self, action:#selector(ViewController.dragBall(_:)))
        view.addGestureRecognizer(dragBall)

    }

    @IBAction func dragBall(recognizer: UIPanGestureRecognizer) {
        let point = recognizer.locationInView(self.view);
        let earthX = Double(point.x)
        let earthY = Double(point.y)
        let midViewXDouble = Double(midViewX)
        let midViewYDouble = Double(midViewY)
        let angleX = (earthX - midViewXDouble)
        let angleY = (earthY - midViewYDouble)
        let angle = tan(angleY/angleX)
        let earthX2 = midViewXDouble + cos(angle)*100
        let earthY2 = midViewYDouble + sin(angle)*100
        circlePath2 = UIBezierPath(arcCenter: CGPoint(x: earthX2,y: earthY2), radius: CGFloat(10), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
        shapeLayer2.path = circlePath2.CGPath
    }
}

The solution is probably in the math I made in the dragBall Func

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Eliko
  • 1,137
  • 4
  • 16
  • 26
  • Eliko, I have problems making this work with autolayout. See http://stackoverflow.com/questions/40885682/my-uiviews-muck-up-when-i-combine-uipangesturerecognizer-and-autolayout – Greg Nov 30 '16 at 13:39

2 Answers2

9

This line is wrong:

    let angle = tan(angleY/angleX)

Since you want to calculate the angle from the coordinates you need the "inverse tangent of two variables"

    let angle = atan2(angleY, angleX)
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Great! It works! Thank you :) But why I need the inverse tangent and not a regular? – Eliko Jun 03 '16 at 12:56
  • 1
    @Eliko: Roughly speaking: `slope = tan(angle)`, `angle = atan(slope)` – Martin R Jun 03 '16 at 13:04
  • @MartinR is [this answer](http://math.stackexchange.com/a/1327368) explains the math? – Yevhen Dubinin Jun 03 '16 at 15:20
  • @EugeneDubinin: It is closely related. The return value of `t = atan2(y, x)` is a solution of the system `cos(t) = x/sqrt(x^2+y^2)`, `sin(t) = y/sqrt(x^2+y^2)`. – Martin R Jun 03 '16 at 19:13
  • Martin R, even with your corrections this won't work with autolayout. See http://stackoverflow.com/questions/40885682/my-uiviews-muck-up-when-i-combine-uipangesturerecognizer-and-autolayout – Greg Nov 30 '16 at 13:40
  • @Greg: As far as I can see, the math is still correct. I wrote an answer to your question which solved the issues in my quick test. – Martin R Nov 30 '16 at 14:31
7

SWIFT 3 Updated code

var midViewX = CGFloat()
var midViewY = CGFloat()

var circlePath2 = UIBezierPath()
var shapeLayer2 = CAShapeLayer()


override func viewDidLoad() {

    super.viewDidLoad()
    midViewX = view.frame.midX
    midViewY = view.frame.midY
    // Do any additional setup after loading the view, typically from a nib.
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: midViewX,y: midViewY), radius: CGFloat(100), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = circlePath.cgPath
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = UIColor.red.cgColor
    shapeLayer.lineWidth = 3.0
    view.layer.addSublayer(shapeLayer)

    var angleEarth: Double = 180
    var angleEarthAfterCalculate: CGFloat = CGFloat(angleEarth*M_PI/180) - CGFloat(M_PI/2)
    var earthX = midViewX + cos(angleEarthAfterCalculate)*100
    var earthY = midViewY + sin(angleEarthAfterCalculate)*100
    circlePath2 = UIBezierPath(arcCenter: CGPoint(x: earthX,y: earthY), radius: CGFloat(10), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
    shapeLayer2.path = circlePath2.cgPath
    shapeLayer2.fillColor = UIColor.blue.cgColor
    shapeLayer2.strokeColor = UIColor.clear.cgColor
    shapeLayer2.lineWidth = 7
    view.layer.addSublayer(shapeLayer2)

    let dragBall = UIPanGestureRecognizer(target: self, action:#selector(dragBall(recognizer:)))
    view.addGestureRecognizer(dragBall)

}

func dragBall(recognizer: UIPanGestureRecognizer) {
    let point = recognizer.location(in: self.view);
    let earthX = Double(point.x)
    let earthY = Double(point.y)
    let midViewXDouble = Double(midViewX)
    let midViewYDouble = Double(midViewY)
    let angleX = (earthX - midViewXDouble)
    let angleY = (earthY - midViewYDouble)
    //let angle = tan(angleY/angleX)
    let angle = atan2(angleY, angleX)
    let earthX2 = midViewXDouble + cos(angle)*100
    let earthY2 = midViewYDouble + sin(angle)*100
    circlePath2 = UIBezierPath(arcCenter: CGPoint(x: earthX2,y: earthY2), radius: CGFloat(10), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
    shapeLayer2.path = circlePath2.cgPath
}