HirsuteJim
HirsuteJim

Reputation: 676

Unable to display `CMMotionManager.attitude` in a TextView?

My app is dependent on how the iPhone is held. For many reasons, I need to use the device sensors with CoreMotion and the .attitude property. Reading the device's attitude is only used for a few times/seconds when the user will set the configuration for my app.

I have tried both pulling and pushing the motion data. In both cases, I can't figure out how to store the CMAttitude values in a way that lets me access them elsewhere.

In the code below…

Here is my code…

import SwiftUI
import CoreMotion

let manager = CMMotionManager()
let queue = OperationQueue()

struct ContentView: View {
   
   @State var myAttitude: CMAttitude = CMAttitude()
   
   var body: some View {
      VStack {
         Image(systemName: "globe")
            .imageScale(.large)
            .foregroundStyle(.tint)
         Text("Hello, world!")
         Button(action: {
            
            guard manager.isDeviceMotionAvailable else { return }
            
            manager.startDeviceMotionUpdates(to: queue) { (motion, error) in
               guard let motion else {
                  return
               }
               
               let currentAttitude = motion.attitude
               myAttitude = currentAttitude
               print("🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒")
               print("Attitude-Pitch: \(myAttitude)")//  ←←← These Work Fine!
               print("Attitude-Pitch: \(myAttitude.pitch.formatted(.number.precision(.fractionLength(5))))")
               print("Attitude-Yaw: \(myAttitude.yaw.formatted(.number.precision(.fractionLength(5))))")
               print("Attitude-Roll: \(myAttitude.roll.formatted(.number.precision(.fractionLength(5))))")
               print("πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄")
            }
         }, label: {
            Text("Check Your Attitude")
               .foregroundStyle(.cyan)
         })
         Text("Attitude-Pitch: \(myAttitude)") //  ←←← App Crashes Here!
//         Text("Attitude-Pitch: \(myAttitude.pitch.formatted(.number.precision(.fractionLength(5))))")
//         Text("Attitude-Yaw: \(myAttitude.yaw.formatted(.number.precision(.fractionLength(5))))")
//         Text("Attitude-Roll: \(myAttitude.roll.formatted(.number.precision(.fractionLength(5))))")
      }
      .padding()
   }
}

#Preview {
   ContentView(myAttitude: CMAttitude())
}

Upvotes: 1

Views: 96

Answers (2)

Alexander Volkov
Alexander Volkov

Reputation: 8407

nil as a default

@State var myAttitude: CMAttitude?

and update the states in the main queue:

DispatchQueue.main.async {
    myAttitude = currentAttitude
}

Upvotes: -1

lorem ipsum
lorem ipsum

Reputation: 29614

There are 2 issues with your code

  1. @State should always be marked private because the memberwise initializer is considered unsafe.

https://developer.apple.com/documentation/swiftui/state

  1. While CMAttitude() compiles, it is not documented so you should not use it. It comes from NSObject it doesn't really belong to CMAttitude (This is a bug).

https://developer.apple.com/documentation/coremotion/cmattitude

Once you correct both those issues your code will no longer crash.

import SwiftUI
import CoreMotion

let manager = CMMotionManager()
let queue = OperationQueue()

struct SampleAttitudeView: View {
    //State should always be private.
    // Mark optional
    @State private var myAttitude: CMAttitude?
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            Button(action: {
                
                guard manager.isDeviceMotionAvailable else { return }
                
                manager.startDeviceMotionUpdates(to: queue) { (motion, error) in
                    guard let motion else {
                        return
                    }
                    
                    let currentAttitude = motion.attitude
                    myAttitude = currentAttitude
                    print("🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒🟒")
                    print("Attitude-Pitch: \(myAttitude!)")//  ←←← These Work Fine!
                    print("Attitude-Pitch: \(myAttitude!.pitch.formatted(.number.precision(.fractionLength(5))))")
                    print("Attitude-Yaw: \(myAttitude!.yaw.formatted(.number.precision(.fractionLength(5))))")
                    print("Attitude-Roll: \(myAttitude!.roll.formatted(.number.precision(.fractionLength(5))))")
                    print("πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄πŸ”΄")
                }
            }, label: {
                Text("Check Your Attitude")
                    .foregroundStyle(.cyan)
            })
            // Show the attitude once it is available.
            if let myAttitude {
                Text("Attitude-Pitch: \(myAttitude)") //  ←←← App  Crashes Here!
                Text("Attitude-Pitch: \(myAttitude.pitch.formatted(.number.precision(.fractionLength(5))))")
                Text("Attitude-Yaw: \(myAttitude.yaw.formatted(.number.precision(.fractionLength(5))))")
                Text("Attitude-Roll: \(myAttitude.roll.formatted(.number.precision(.fractionLength(5))))")
            } else {
                Text("Waiting for Attitude")
            }
            
        }
        .padding()
    }
}

#Preview {
    SampleAttitudeView()
}

Upvotes: 0

Related Questions