Moonwalker4z Moonwalker4z - 17 days ago 7
Swift Question

Swift gyroscope yaw, pitch, roll

I'm doing a project for my school, for a programming subject. I'm working in Xcode in Swift. I would like to make an application that uses Gyroscope. I don't know but somehow it won't run on my iphone because of some errors in Xcode that I don't know how to fix.When I run the program is says "fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)"
The main thing of my project is to display rotations (yaw,pitch,roll) in degrees on iphone screen using gyroscope. I would be really grateful if someone help me with this. Thank you in advance.

The code in my ViewController:

import UIKit
import CoreMotion

class ViewController: UIViewController {

var currentMaxRotX: Double = 0.0
var currentMaxRotY: Double = 0.0
var currentMaxRotZ: Double = 0.0

let motionManager = CMMotionManager()

@IBOutlet var rotX : UILabel! = nil
@IBOutlet var rotY : UILabel! = nil
@IBOutlet var rotZ : UILabel! = nil

@IBOutlet var maxRotX : UILabel! = nil
@IBOutlet var maxRotY : UILabel! = nil
@IBOutlet var maxRotZ : UILabel! = nil


@IBOutlet weak var RollLabel: UILabel! = nil

@IBOutlet weak var PitchLabel: UILabel! = nil

@IBOutlet weak var YawLabel: UILabel! = nil

override func viewDidLoad() {

if motionManager.gyroAvailable {

if !motionManager.gyroActive {
motionManager.gyroUpdateInterval = 0.2
motionManager.startGyroUpdates()
//motionManager = [[CMMotionManager alloc]init]; should I use this ???
//motionManager.deviceMotionUpdateInterval = 0.2;
//motionManager.startDeviceMotionUpdates()

motionManager.startGyroUpdatesToQueue(NSOperationQueue.currentQueue(), withHandler: {(gyroData: CMGyroData!, error: NSError!)in
self.outputRotationData(gyroData.rotationRate)
if (error != nil)
{
println("\(error)")
}
})


}
}else

{
var alert = UIAlertController(title: "No gyro", message: "Get a Gyro", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}


super.viewDidLoad()
}

// radians to degrees
func radians(fromDegrees degrees: Double) -> Double {
return 180 * degrees / M_PI
}

func outputRotationData(rotation:CMRotationRate)
{
rotX.text = NSString(format:"Rotation X: %.4f",rotation.x)
if fabs(rotation.x) > fabs(currentMaxRotX)
{
currentMaxRotX = rotation.x
}

rotY.text = NSString(format:"Rotation Y: %.4f", rotation.y)
if fabs(rotation.y) > fabs(currentMaxRotY)
{
currentMaxRotY = rotation.y
}
rotZ.text = NSString(format:"Rotation Z:%.4f", rotation.z)
if fabs(rotation.z) > fabs(currentMaxRotZ)
{
currentMaxRotZ = rotation.z
}

maxRotX.text = NSString(format:"Max rotation X: %.4f", currentMaxRotX)
maxRotY.text = NSString(format:"Max rotation Y:%.4f", currentMaxRotY)
maxRotZ.text = NSString(format:"Max rotation Z:%.4f", currentMaxRotZ)

var attitude = CMAttitude()
var motion = CMDeviceMotion()
motion = motionManager.deviceMotion
attitude = motion.attitude


YawLabel.text = NSString (format: "Yaw: %.2f", attitude.yaw) //radians to degress NOT WORKING
PitchLabel.text = NSString (format: "Pitch: %.2f", attitude.pitch)//radians to degress NOT WORKING
RollLabel.text = NSString (format: "Roll: %.2f", attitude.roll)//radians to degress NOT WORKING
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Answer

In these two lines you're working with implicitly unwrapped optionals: you use them without ? or !, but they'll crash your program if they're nil. In this case, motionManager.deviceMotion is nil because you haven't called motionManager.startDeviceMotionUpdates() prior to that access.

Your viewDidLoad method is the right place to do this:

override func viewDidLoad() {

    if motionManager.gyroAvailable {
        motionManager.deviceMotionUpdateInterval = 0.2;
        motionManager.startDeviceMotionUpdates()

        motionManager.gyroUpdateInterval = 0.2
        motionManager.startGyroUpdatesToQueue(NSOperationQueue.currentQueue()) {
           [weak self] (gyroData: CMGyroData!, error: NSError!) in

           self?.outputRotationData(gyroData.rotationRate)
           if error != nil {
               println("\(error)")
           }
        }
    } else {
        // alert message
    }

    super.viewDidLoad()
}

There are a few additional changes there:

  1. You don't need to call startGyroUpdates() before startGyroUpdatesToQueue().
  2. Swift uses trailing closures for completion handlers like this one - nicer to write and read.
  3. To avoid a reference cycle, declare self as weak inside a completion handler - this then requires using optional chaining when accessing self.
Comments