Chapter 14. Core Motion

This year, Apple finally brought some long-awaited features into the Core Motion framework. It’s especially exciting that the same capabilities, or some version of them, are also available on the Apple Watch. This is great news for us developers because we can program for the watch in a more native way, rather than reading this data from the user’s iPhone and sending it to the watch with Bluetooth.

There are a couple key terms I’ll be using throughout this chapter that you need to know about:

Cadence
I use a cadence sensor on my bicycle. It helps me figure out how many times I spin my pedals, which can be crucial knowledge. Think about riding downhill on a bicycle, at a 45-degree angle, for 20 minutes, out of a total 40-minute bike ride. Your total calories burned and effort will be miscalculated because you might not even have pedaled when going downhill. The watch actually includes a cadence sensor for running.
Pace
This is a ratio, dividing the time you have moved by the distance. If you’re counting in meters, for instance, your pace might be 0.5 seconds per meter, meaning that you travelled 1 meter in half a second.

iOS devices can provide pace and cadence information when it’s available from the pedometer. Some pedometers might not have this information available. You can call the isPaceAvailable() class function of CMPedometer to check whether pace information is available. Similarly, you can call the isCadenceAvailable() class method of CMPedometer to determine whether cadence information is available.

Note

Import the Core Motion framework into your project before attempting to run the code we write in this chapter.

14.1 Querying Pace and Cadence Information

Problem

You want to get cadence and pace information from the pedometer on an iOS device.

Solution

Follow these steps:

  1. Find out whether cadence and pace are available.
  2. Call the startUpdates(from:withHandler:) function of CMPedometer.
  3. In your handler block, read the currentPace and currentCadence properties of the incoming optional CMPedometerData object.

Discussion

Let’s check out an example:

guard CMPedometer.isCadenceAvailable() &&
  CMPedometer.isPaceAvailable() else{
    print("Pace and cadence data are not available")
    return
}

let oneWeekAgo = Date(timeIntervalSinceNow: -(7 * 24 * 60 * 60))
pedometer.startUpdates(from: oneWeekAgo) {data, error in
  
  guard let pData = data, error == nil else{
    return
  }
  
  if let pace = pData.currentPace{
    print("Pace = (pace)")
  }
  
  if let cadence = pData.currentCadence{
    print("Cadence = (cadence)")
  }
  
}

// remember to stop the pedometer updates with stopPedometerUpdates()
// at some point
            
Note

When you finish querying pedometer data, always remember to call the stopPedometerUpdates() function on your instance of CMPedometer.

14.2 Recording and Reading Accelerometer Data

Problem

You want iOS to accumulate some accelerometer data for a specific number of seconds and then batch-update your app with all the accelerometer data in one go.

Solution

Follow these steps:

  1. Call the isAccelerometerRecordingAvailable() class function on CMSensorRecorder and abort if it returns false, because that means that accelerometer recording is not available.
  2. Instantiate CMSensorRecorder.
  3. Call the recordAccelerometer(forDuration:) function on your sensor recorder and pass the number of seconds for which you want to record accelerometer data.
  4. Go into a background thread and wait for your data if you want.
  5. Call the accelerometerData(from:to:) function on your sensor recorder to get the accelerometer data from a given date to another date. The return value of this function is a CMSensorDataList object, which is enumerable. Each item in this enumeration is of type CMRecordedAccelerometerData.
  6. Read the value of each CMRecordedAccelerometerData. You’ll have properties like startDate, timestamp, and acceleration, which is of type CMAcceleration.

Discussion

I mentioned that CMSensorDataList is enumerable. That means it conforms to the NSFastEnumeration protocol, but you can not use the for x in ... syntax on this type of enumerable object. You’ll have to make it conform to the Sequence protocol and implement the makeIterator() function like so:

extension CMSensorDataList : Sequence{
  public func makeIterator() -> NSFastEnumerationIterator {
    return NSFastEnumerationIterator(self)
  }
}
            

So I’m going to first define a lazily allocated sensor recorder. If sensor information is not available, my object won’t hang around in the memory:

lazy var recorder = CMSensorRecorder()
            

Then I check whether sensor information is available:

guard CMSensorRecorder.isAccelerometerRecordingAvailable() else {
  print("Accelerometer data recording is not available")
  return
}
            

Next, I will record my sensor data for a period:

let duration = 3.0
recorder.recordAccelerometer(forDuration: duration)
            

Then I will go to the background and read the data:

OperationQueue().addOperation{[unowned recorder] in
  
  Thread.sleep(forTimeInterval: duration)
  let now = Date()
  let past = now.addingTimeInterval(-(duration))
  guard let data = recorder.accelerometerData(from: past, to: now) else{
    return
  }
  
  print(data)
  
}
            
Note

It is important to enumerate the result of accelerometerData(from:to:) on a non-UI thread, because there may be thousands of data points in the results.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset