iosswiftswiftuicore-motionmagnetometer

How do I use Core Motion to output magnetometer data using SwiftUI?


I am having trouble outputting the data for the magnetometer, accelerometer and gyroscope using Core Motion with SwiftUI. I'm assuming my problem has something to do with the startMagnetometerUpdates().

I've tried using source code found here on stack overflow, as well as on GitHub/google. The problem is all the code I'm finding uses UIKit instead of SwiftUI. Is it possible to implement this without using UIKit?

import CoreMotion

let motionManager = CMMotionManager()
var x = 0.0; var y = 0.0; var z = 0.0

    func magnet() {
  motionManager.magnetometerUpdateInterval = 1/60
  motionManager.startMagnetometerUpdates()
if let magnetometerData = motionManager.magnetometerData {
    x = magnetometerData.magneticField.x
    y = magnetometerData.magneticField.y
    z = magnetometerData.magneticField.z
}
}

struct Magnetometer: View {
        var body: some View {
            VStack {
                Text("Magnetometer Data")
                Text("X: \(x)")
                Text("Y: \(y)")
                Text("Z: \(z)")
            }
        }
    }

struct Magnetometer_Previews: PreviewProvider {
    static var previews: some View {
        Magnetometer()
    }
}

The output should just display the x, y and z values for the sensor and update on an interval of 1/60. The current output is 0.00000 for every value, which is because I set each variable to 0 already.


Solution

  • You have a couple of problems with your code.

    Your first problem is that you need a binding between your model data and your view - By creating a binding, the view will be updated automatically when the model changes.

    The second problem is that you are only accessing the magnetometer data once via motionManager.magnetometerData rather than setting up a closure to monitor updates via startMagnetometerUpdates(to:withHandler:).

    You can use ObservableObject from the Combine framework and @ObservedObject in your view to create the appropriate binding.

    Start by creating a class to wrap your motion manager:

    import Foundation
    import Combine
    import CoreMotion
    
    class MotionManager: ObservableObject {
    
        private var motionManager: CMMotionManager
    
        @Published
        var x: Double = 0.0
        @Published
        var y: Double = 0.0
        @Published
        var z: Double = 0.0
    
    
        init() {
            self.motionManager = CMMotionManager()
            self.motionManager.magnetometerUpdateInterval = 1/60
            self.motionManager.startMagnetometerUpdates(to: .main) { (magnetometerData, error) in
                guard error == nil else {
                    print(error!)
                    return
                }
    
                if let magnetData = magnetometerData {
                    self.x = magnetData.magneticField.x
                    self.y = magnetData.magneticField.y
                    self.z = magnetData.magneticField.z
                }
    
            }
    
        }
    }
    

    This class conforms to ObservableObject and @Publishes its three properties, x,y and z.

    Simply assigning new values to these properties in the magnetometer update closure will cause the publisher to fire and update any observers.

    Now, in your view, you can declare an @ObservedObject for your motion manager class and bind the properties.

    struct ContentView: View {
    
        @ObservedObject
        var motion: MotionManager
    
        var body: some View {
            VStack {
                Text("Magnetometer Data")
                Text("X: \(motion.x)")
                Text("Y: \(motion.y)")
                Text("Z: \(motion.z)")
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView(motion: MotionManager())
        }
    }
    

    Note that you will need to pass an instance of MotionManager in your SceneDelegate.swift file:

    let contentView = ContentView(motion: MotionManager())