Swift 3D rotation of UIImageView (CGAffineTransform or CATransform3D)

1.5k Views Asked by At

I have an image that I want to rotate, about its centre point, in 3D depending on the roll/pitch/yaw of the iPhone.

Which is the preferred solution: CGAffineTransform or CATransform3D?

Are there any examples I can follow. There are numerous posts that allude towards a solution.

The image for simplicity's sake the image is a rectangle.

Many thanks.

1

There are 1 best solutions below

0
Paul B On

CoreMotion gives device's current attitude (with yaw, pitch ,roll, in eulerian mode or as quaternion rotation). These are 3 different ways to express the same thing. What you need to do is to apply the transformations correctly to reflect the device's current attitude. Consider this sample.

import UIKit
import CoreMotion

class ViewController: UIViewController {

    @IBOutlet weak var rect1: UIView! // Add background color to be ale to see it
    @IBOutlet weak var rect2: UIView!

    @IBOutlet weak var xLabel: UILabel!
    @IBOutlet weak var yLabel: UILabel!
    @IBOutlet weak var zLabel: UILabel!


    let motionManager = CMMotionManager() // The one and only for the app

    override func viewDidLoad() {
        super.viewDidLoad()
        //The device motion object provides the device orientation in several equivalent forms: Euler angles, a rotation matrix, and a quaternion. Since we already use matrices to represent rotations in our Metal code, they are they best fit for incorporating Core Motion’s data into our app.
        let motionHandler: CMDeviceMotionHandler = { [weak self] (motion, error)  in
            if let err = error { print(err) }
            if let m = motion {
                self?.xLabel.text = "Yaw: \(m.attitude.yaw)"
                self?.yLabel.text = "Pitch: \(m.attitude.pitch)"
                self?.zLabel.text = "Roll: \(m.attitude.roll)"

                var transform: CATransform3D

                transform = CATransform3DMakeRotation(CGFloat(m.attitude.pitch), 1, 0, 0) // X rotation
                transform = CATransform3DRotate(transform, CGFloat(m.attitude.roll), 0, 1, 0) // Y rotation
                transform = CATransform3DRotate(transform, CGFloat(m.attitude.yaw), 0, 0, 1) // Z rotation
                print(transform)

                self?.rect1.layer.transform = transform
                // Mind coordinate space is most intuitive. The following transform differs.
                // If you can tell why - please do.
                let rm = m.attitude.rotationMatrix

                transform = CATransform3D(m11: CGFloat(rm.m11), m12: CGFloat(rm.m12), m13: CGFloat(rm.m13), m14: 0,
                                          m21: CGFloat(rm.m21), m22: CGFloat(rm.m22), m23: CGFloat(rm.m23), m24: 0,
                                          m31: CGFloat(rm.m31), m32: CGFloat(rm.m32), m33: CGFloat(rm.m33), m34: 0,
                                          m41: 0,               m42: 0,               m43: 0,               m44: 1)
                print(transform)


                self?.rect2.layer.transform = transform
                //self?.compass.layer.sublayerTransform = transform
                //[myImage setNeedsDisplay];
            }
        }
        motionManager.deviceMotionUpdateInterval = 0.1

        if let current = OperationQueue.current {
            motionManager.startDeviceMotionUpdates(using: .xArbitraryCorrectedZVertical/*.xTrueNorthZVertical*/, to: current, withHandler: motionHandler)
        }

    }
}

To reproduce it is necessary to create UIViews (rect1, rect2) in the Storyboard, hook them up to the viewController. As you rotate the device, these views will be transformed accordingly. You may try CMQuaternion (m.attitude.quaternion) to do similar job.