Search code examples
iosiphoneios10core-motioncmmotionmanager

Why does CMMotionManager report incorrect yaw after stopping and restarting?


I'm using CMMotionManager to get device attitude, and running into a peculiar problem where it works perfectly the first time it's started, but after stopping and starting it gives doubled numbers for yaw.

I start it using startDeviceMotionUpdates(using: .xTrueNorthZVertical) and check the yaw values in the render loop of a SceneKit scene. If I put the phone flat on a table and rotate it 90°, it shows a change of 90° in yaw. Perfect.

If I stop it using stopDeviceMotionUpdates(), start it again, and perform the same 90° rotation, I expect to see the same change of 90°. But instead, it shows 180° of rotation!

This only seems to be happening when starting it the first time with the (using:) syntax. I'm testing it on an iPhone 5 with iOS 10.0.2, using xCode 8.0.

To recreate, create a new project using the "Game" template and replace the code in GameViewController.swift with the following:

import UIKit
import SceneKit
import CoreMotion

class GameViewController: UIViewController, SCNSceneRendererDelegate {

    static let motionManager = CMMotionManager()

    var scnView: SCNView!
    var scnScene: SCNScene!

    override func viewDidLoad() {
        super.viewDidLoad()
        scnView = self.view as! SCNView
        scnView.delegate = self
        scnScene = SCNScene()
        scnView.scene = scnScene
        scnView.isPlaying = true

        GameViewController.motionManager.startDeviceMotionUpdates(using: .xTrueNorthZVertical)
        // stop and start it again to cause doubling
        GameViewController.motionManager.stopDeviceMotionUpdates()
        GameViewController.motionManager.startDeviceMotionUpdates(using: .xTrueNorthZVertical)

    }

    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        if let motion = GameViewController.motionManager.deviceMotion {
            print(Int(motion.attitude.yaw * 180/M_PI))
        }
    }
}

This code starts CMMotionManger, then immediately stops it and starts it again. Rotate the phone through 90° and you'll see that it shows 180° of yaw. Obviously this is not a practical example, just a simplified version that shows the issue. In my actual use case, I stop CMMotionManager when the SceneKit view I'm using it in is dismissed, and restart it when another SceneKit view is opened.

For now my workaround is to never stop CMMotionManager once it's started. It works, but the documentation specifically says "You must call stopDeviceMotionUpdates() when you no longer want your app to process device-motion updates.", so this does not seem like a great solution.

Why is CMMotionManager giving wrong values after stopping and starting, and what can I do to fix it?


Solution

  • This behavior vanished after installing iOS 10.1, so presumably it was a bug that has been fixed.