Search code examples
swiftcore-motion

CoreMotion Heading Inconsistent


I have used the CoreMotion CMMotionManager motion.heading for some time now and it seems when I close my app and reopen it after some time, I will need to recalibrate the compass. For example I have a screen with an arrow pointing towards motion.heading. Initially I point my phone at a visible location 100m away and it points directly towards it. In some time, I can orient the phone the exact same way but the compass arrow is different. The initial app open, the phone points north (towards the landmark) and the arrow also points north. If the app is reopened (from either full or not fully closed) the phone points north towards the landmark, but the arrow points elsewhere.

So in practice I have recreated all variables I can with the phone's orientation, landmark location, me holding the phone in relation to the landmark, yet the heading will report a different value sometimes.

I use this code currently to get the heading information:

import CoreMotion
import SwiftUI

class Compass: ObservableObject {
  private var motionManager: CMMotionManager
  @Published var heading: Double = 0.0

  init() {
    self.motionManager = CMMotionManager()
    self.startUpdates()
  }
  
  private func startUpdates() {
    if motionManager.isDeviceMotionAvailable {
      motionManager.deviceMotionUpdateInterval = 0.1
      motionManager.startDeviceMotionUpdates(to: .main) { [weak self] (motion, error) in 
        guard let motion = motion else { return }
        
        self?.heading = heading.truncatingRemainder(dividingBy: 360) // normalize for 360 degrees of possible heading rotation
    }
  }
}

And the arrow is just displayed towards the landmark with a distance formula with heading involved. But the heading value changes. Is there a better or more consistent way to be getting the direction the phone is pointed than this? Like using magnetic north or something weird like that?

The compass app always displays the same thing regarding compass north, so why would the motion.heading display a different value sometimes when the compass app still works fine?


Solution

  • try this approach using a different startDeviceMotionUpdates, such as:

    import Foundation
    import CoreMotion
    import SwiftUI
    
    
    struct ContentView: View {
        @StateObject private var compass = Compass()
        
        var body: some View {
            Text("\(compass.heading)")
        }
    }
    
    
    class Compass: ObservableObject {
        let motionManager = CMMotionManager()
        
        @Published var heading: Double = 0.0
        
        init() {
            self.startUpdates()
        }
        
        private func startUpdates() {
            if motionManager.isDeviceMotionAvailable {
                motionManager.deviceMotionUpdateInterval = 1.0
                // --- here
                motionManager.startDeviceMotionUpdates(using: .xTrueNorthZVertical, to: .main) { (motion, error) in
                    guard let motion = motion else { return }
                    print("---> heading: \(motion.heading)")
                    self.heading = motion.heading
                }
            }
        }
    }