Reputation: 65
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.
Upvotes: 5
Views: 4178
Reputation: 114875
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 @Publish
es 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())
Upvotes: 13
Reputation: 14388
You could use Combine to build a class to provide the data:
import Combine
public class MagneicFieldProvider: NSObject, ObservableObject {
public let objectWillChange = PassthroughSubject<CMMagneticField,Never>()
public private(set) var magneticField: CMMagneticField = CMMagneticField() {
willSet {
objectWillChange.send(newValue)
}
}
deinit {
motionManager.stopMagnetometerUpdates()
}
private let motionManager: CMMotionManager
public override init(){
self.motionManager = CMMotionManager()
super.init()
motionManager.magnetometerUpdateInterval = 1/60
}
public func startUpdates() {
motionManager.startMagnetometerUpdates(to: OperationQueue.main) { this, that in
if let magneticField = self.motionManager.magnetometerData?.magneticField {
self.magneticField = magneticField
}
}
}
}
and then use it in your View:
struct MagnetometerView: View {
@ObservedObject var magnetometer = MagneicFieldProvider()
var body: some View {
VStack(alignment: .leading) {
Text("Magnetometer Data")
Text("X: \(magnetometer.magneticField.x)")
Text("Y: \(magnetometer.magneticField.y)")
Text("Z: \(magnetometer.magneticField.z)")
} .onAppear(perform: { self.magnetometer.startUpdates() })
}
}
Upvotes: 3