Obtain absolute rotation using CMDeviceMotion?

I’m building a simple game with Sprite Kit, the screen doesn’t rotate but I want to know the angle the user is holding the phone for a game mechanic.

The values I want to get can be easily retrieved with the accelerometer (x, y) but I have found this to be unreliable so I’m trying to archive better results with CMDeviceMotion. I could obtain the equivalent to data.acceleration.y but I can’t figure out how to get the equivalent of data.acceleration.x.

  • “pushes” matching UIInterpolatingMotionEffect? (i.e. access “physics” on UIInterpolatingMotionEffect)
  • What exactly does the iPhone accelerometer measure?
  • How to program smooth movement with the accelerometer like a labyrinth game on iPhone OS?
  • Tap pressure strength detection using CPBPressureTouchGestureRecognizer
  • Detecting Acceleration in a car (iPhone Accelerometer)
  • Point in Tilt Direction - iPhone
  • if let data = motionManager.accelerometerData? {
        let y = CGFloat(data.acceleration.y)
        let x = CGFloat(data.acceleration.x)
    }
    
    // Device Motion
    if let attitude = motionManager.deviceMotion?.attitude? {
        let y = CGFloat(-attitude.pitch * 2 / M_PI) // This matches closely with data.acceleration.y
        let x = ??????????
    }
    

    How do I calculate the equivalent to data.acceleration.x using CMDeviceMotion?

    3 Solutions Collect From Internet About “Obtain absolute rotation using CMDeviceMotion?”

    It sounds like you want the motion manager’s device motion, if you want the rotation values to take gyro and accelerometer input into account. According to the docs:

    An instance of CMDeviceMotion encapsulates measurements of the attitude, rotation rate, and acceleration of a device.

    So, instead of monitoring gyro data directly, monitor device motion instead. The example below demonstrates how to obtain device motion on a frame by frame basis. I’ve just decided to print the CMAttitude object directly, but from this object, you can directly access the pitch, roll, and yaw of the device (and more) which if I’m not mistaken, is exactly what you’re looking for.

    import SpriteKit
    import CoreMotion
    
    class GameScene: SKScene {
        let motionManager = CMMotionManager()
    
        override func didMoveToView(view: SKView) {
            motionManager.deviceMotionUpdateInterval = 1.0 / 30.0
            motionManager.startDeviceMotionUpdates()
        }
    
        override func willMoveFromView(view: SKView!) {
            motionManager.stopDeviceMotionUpdates()
        }
    
        override func update(currentTime: CFTimeInterval) {
            if let attitude = motionManager.deviceMotion?.attitude? {
                println(attitude)
                let y = CGFloat(-attitude.pitch * 2 / M_PI)
                let x = CGFloat(-attitude.roll * 2 / M_PI)
            }
        }
    }
    

    If you’re using Swift 2, there are some minor changes that need to be made, which are shown below.

    class GameScene: SKScene {
        let motionManager = CMMotionManager()
    
        override func didMoveToView(view: SKView) {
            motionManager.deviceMotionUpdateInterval = 1.0 / 30.0
            motionManager.startDeviceMotionUpdates()
        }
    
        override func willMoveFromView(view: SKView) {
            motionManager.stopDeviceMotionUpdates()
        }
    
        override func update(currentTime: CFTimeInterval) {
            if let attitude = motionManager.deviceMotion?.attitude {
                print(attitude)
                let y = CGFloat(-attitude.pitch * 2 / M_PI)
                let x = CGFloat(-attitude.roll * 2 / M_PI)
            }
        }
    }
    

    For more info on this, consult this image.

    enter image description here

    You can get the angle by reading the gyro data provided by CoreMotion. Initialize a CM object like so:

    class GameScene: SKScene {
        var motionManager = CMMotionManager()
        override func didMoveToView(view: SKView) {
            motionManager.startGyroUpdates()
        }
    }
    

    Then in the update function, you can read it:

    override func update(currentTime: CFTimeInterval) {
        if let data = motionManager.gyroData {
            let x = data.rotationRate.x
            let y = data.rotationRate.y
            let z = data.rotationRate.z
        }
    }
    

    You can now use the x, y and z values for your game mechanic. However, note that it’s giving the current rotation rate and not the absolute rotation. You could keep track of it yourself if that’s what you need. Alternatively you could use the accelerometer data:

    motionManager.startAccelerometerUpdates()
    ...
    if let data = motionManager.accelerometerData {
        let x = data.acceleration.x
    }
    

    This is what I’m trying now. I got to it through experimentation so I cannot tell if it’s correct, probably there is a better solution.

                if let attitude = motionManager.deviceMotion?.attitude? {
                     // Convert pitch and roll to [-1, 1] range
                    let pitch = CGFloat(-attitude.pitch * 2 / M_PI)
                    let roll = CGFloat(abs(attitude.roll) > M_PI / 2 ? (2 - abs(attitude.roll) * 2 / M_PI) * (attitude.roll > 0 ? 1 : -1) : attitude.roll * 2 / M_PI)
    
                    // Calculate x and y
                    let y = pitch
                    let x = roll*(1.0-abs(pitch))
                }