0
votes

Rotating around point is simple: One step affine transform for rotation around a point?

However, I've encountered a problem: it doesn't work when "orbiting object" is rotated before. Here's the code:

import UIKit

class ViewController: UIViewController {

    var orbitingImageView: UIImageView!
    var centerImageView: UIImageView!

    let rr: CGFloat = .pi / 4

    override func viewDidLoad() {
        super.viewDidLoad()

        centerImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 150))
        centerImageView.center = CGPoint(x: 300, y: 400)
        centerImageView.backgroundColor = .red
        view.addSubview(centerImageView)

        orbitingImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        orbitingImageView.center = CGPoint(x: 500, y: 400)
        orbitingImageView.backgroundColor = .blue
        view.addSubview(orbitingImageView)

        orbitingImageView.transform = orbitingImageView.transform
            .rotated(by: rr) //without it works fine

        n()
    }

    func n() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { //little trick to make an animation

            let angle: CGFloat = 0.05

            let orb = self.orbitingImageView!
            let cen = self.centerImageView!
            let a = CGPoint(x: cen.center.x - orb.center.x,
                            y: cen.center.y - orb.center.y)

            self.orbitingImageView.transform = self.orbitingImageView.transform
                .translatedBy(x: a.x, y: a.y)
                .rotated(by: angle)
                .translatedBy(x: -a.x, y: -a.y)

            self.n()
        }
    }


}

extension CGAffineTransform { //helper methods

    func getScale() -> CGFloat {
        return (self.a * self.a + self.c * self.c).squareRoot()
    }

    func getRotation() -> CGFloat {
        return atan2(self.b, self.a)
    }

}

Now, I can modify n() function like this:

func n() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { //little trick to make an animation

        let angle: CGFloat = 0.05

        let orb = self.orbitingImageView!
        let cen = self.centerImageView!
        let a = CGPoint(x: cen.center.x - orb.center.x,
                        y: cen.center.y - orb.center.y)

        self.orbitingImageView.transform = self.orbitingImageView.transform
            .rotated(by: -self.rr)
            .translatedBy(x: a.x, y: a.y)
            .rotated(by: angle)
            .translatedBy(x: -a.x, y: -a.y)
            .rotated(by: self.rr)

        self.n()
    }
}

And it works again. However, this method requires to store initial angle (variable rr).

How can I calculate rr?

This is just a simplified version of code which I use it in the "real" project. There I can pan/pinch/rotate multiple objects on the screen. If, for example, I've selected 2 objects the first object (on which I have my finger) should rotate by its center while the second one should rotate around center of the first point. And then it's this problem, because if second object was rotated before alone (by it's center) I need this rotation (which is rr here).

1

1 Answers

0
votes

Finally, I've found a solution. Instead of calculating rr I've replaced 3-step rotation around point with 1-step method:

let tr = CGAffineTransform(
    a: cos(angle),
    b: sin(angle),
    c: -sin(angle),
    d: cos(angle),
    tx: a.x - a.x * cos(angle) + a.y * sin(angle),
    ty: a.y - a.x * sin(angle) - a.y * cos(angle)
)

self.orbitingImageView.transform = self.orbitingImageView.transform
    .concatenating(tr)

Whole code:

import UIKit

class ViewController: UIViewController {

    var orbitingImageView: UIImageView!
    var centerImageView: UIImageView!

    let rr: CGFloat = .pi / 4

    override func viewDidLoad() {
        super.viewDidLoad()

        centerImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 150))
        centerImageView.center = CGPoint(x: 300, y: 400)
        centerImageView.backgroundColor = .red
        view.addSubview(centerImageView)

        orbitingImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        orbitingImageView.center = CGPoint(x: 500, y: 400)
        orbitingImageView.backgroundColor = .blue
        view.addSubview(orbitingImageView)


        orbitingImageView.transform = orbitingImageView.transform
            .rotated(by: rr)
            .scaledBy(x: 0.5, y: 0.5) //even works with prescaling

        n()
    }

    func n() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {

            let angle: CGFloat = 0.05

            let orb = self.orbitingImageView!
            let cen = self.centerImageView!

            let a = CGPoint(x: cen.center.x - orb.center.x,
                            y: cen.center.y - orb.center.y)

            let tr = CGAffineTransform(
                a: cos(angle),
                b: sin(angle),
                c: -sin(angle),
                d: cos(angle),
                tx: a.x - a.x * cos(angle) + a.y * sin(angle),
                ty: a.y - a.x * sin(angle) - a.y * cos(angle)
            )

            self.orbitingImageView.transform = self.orbitingImageView.transform
                .concatenating(tr)

            self.n()
        }
    }

}

Now it works with pre-rotation as well as with pre-scaling.