Ronald Baks
Ronald Baks

Reputation: 37

Swift CGAffineTransform rotation jumps

I used the information from How to rotate an UIImageView using TouchesMoved in my own project. But I found a problem, and I can’t figure out how to solve it.

The view in the original post has the same problem but with the small view it is almost not noticeable. A made this example code. See how the view “jumps”. The touch point is always left of the anchor point. How can I avoid this jump.

class ViewController: UIViewController {
    var myView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        myView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 200))
        myView.center = self.view.center
        myView.isUserInteractionEnabled = true
        myView.backgroundColor = UIColor.red
        self.view.addSubview(myView)
        myView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)

    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch: UITouch = touches.first!

        if touch.view === myView {
            let position = touch.location(in: self.view)
            let target = myView.center
            let angle = atan2(target.y-position.y, target.x-position.x)
            myView.transform = CGAffineTransform(rotationAngle: angle)
        }
    }
}

Upvotes: 0

Views: 936

Answers (2)

matt
matt

Reputation: 535306

You have two major problems:

  • You are not taking into account where the original touch was. You need to record that information in touchesBegan and work out the angle of your rotation transform with respect to that.

  • You are saying let target = myView.center as if this were the point we are rotating around. But it isn't, because you moved the anchorPoint of myView.

Here's working code (taken from my book) that lets you drag to rotate myView around its own center:

class ViewController: UIViewController {
    var myView: UIView!
    override func viewDidLoad() {
        super.viewDidLoad()
        myView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 200))
        myView.center = self.view.center
        myView.isUserInteractionEnabled = true
        myView.backgroundColor = UIColor.red
        self.view.addSubview(myView)
    }
    func pToA (_ t:UITouch) -> CGFloat {
        let loc = t.location(in: myView)
        let c = myView.convert(myView.center, from:myView.superview!)
        return atan2(loc.y - c.y, loc.x - c.x)
    }
    var initialAngle = CGFloat(0)
    var angle = CGFloat(0)
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.initialAngle = pToA(touches.first!)
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let ang = pToA(touches.first!) - self.initialAngle
        let absoluteAngle = self.angle + ang
        myView.transform = myView.transform.rotated(by: ang)
        self.angle = absoluteAngle
    }
}

But if you really want to rotate around a different point (moving the anchor point as your code does), you'd need to adjust that code accordingly.

Upvotes: 3

Tim Kokesh
Tim Kokesh

Reputation: 879

It looks like your issue is in the line

let angle = atan2(target.y-position.y, target.x-position.x)

atan2 returns a number between 0 and π, rather than what you want, a number between -π and π. You should be able to fix it by comparing (target.y - position.y) with 0. When it is greater than zero, your original returned value should be good. When it is less than zero, you will want to subtract π to get the correct angle.

Upvotes: -1

Related Questions