Chapter    4

Using Core Motion to Save Motion Data

Ahmed Bakir

Introduction

In the last chapter, you learned how to set up an application for HealthKit, Apple’s shared repository for health data, and query for specific health data types. In this chapter, you will learn how to use Core Motion to access live motion data from a user’s device, and how to save it back to HealthKit, where it will be accessible to all applications.

In this chapter, you continue to expand the RunTracker app that you started in Chapter 3. In this chapter, we cover the following concepts:

  • How to access hardware using Core Motion
  • How to save information collected over time using Core Motion
  • How to save information to HealthKit
  • How to receive real-time activity updates from Core Motion and HealthKit

As with the other projects in this book, you can find the complete source code for the RunTracker project in the Ch4 folder of the Source Code bundle available on this book’s web page at Apress.com/9781484211953.

Using Core Motion to Access Motion Hardware

In Chapter 3, we set up the workout table view and HealthKit permissions for RunTracker; now it is time to move on to collecting data that you can save back to HealthKit. Saving data to HealthKit works much like retrieving it: you specify a data type, a quantity of data collected, and a time range. For the RunTracker application, you will use the Core Motion framework to access the pedometer on the user’s device. From the pedometer, you can detect the number of steps the user has traveled and even the type of activity the user is engaged in (walking, running, bicycling). You will take advantage of both of these in creating the interface for the RunTracker application.

Core Motion has been iOS’s motion-sensing framework since iOS 4. Developers have been using it for years to access the built-in accelerator and gyroscope (enabling thousands of racing games ever since). However, until the M-series of motion coprocessors, there was never an easy way to access step data. You could use the accelerator to detect when the device experienced a “bump” in movement, but you had to develop your own code to define a “step.” Similarly, you could use a GPS to determine how far a user moved, but doing so would drain the battery quickly.

Although iOS 7 added an interface to the M-series motion coprocessor to the Core Motion framework, iOS 8 fully unlocked it, allowing you to retrieve data directly as steps and activity types. Core Motion takes care of all the work to define what a step is, what it corresponds to in terms of distance, and what the user was doing when the step was registered (for example, running or walking).

Swift streamlines the process of including common frameworks into your project, so you do not need to add Core Motion manually to our project to begin using it. However, you will need to include it in the classes that need to access its application programming interfaces (APIs). For the RunTracker application, the CreateWorkoutViewController class is responsible for pulling fitness data from Core Motion and displaying statistics during a workout. Begin by modifying the class definition to include the framework as shown in Listing 4-1.

Requesting User Permission for Motion Activity

Although not explicitly defined as a device capability in your project settings, you implement Core Motion using the same design pattern. In order to use Core Motion, you need to

  • Verify that Core Motion is available on a user’s device
  • Ask the user for permission to access Core Motion
  • Verify that the desired hardware is available on a user’s device
  • Ask the user for permission to access the hardware

The Core Motion class that manages motion events and the motion activity permission is CMMotionActivityManager. Strictly speaking, a motion activity is defined as an event that corresponds to the type of movement the user is currently engaged in, whether that is walking, running, or even driving. You query for motion activity status by calling the public method isActivityAvailable(). This method returns a bool value, indicating whether the user has given your app permission to access motion activity.

The first time you ask the user for Core Motion permission, he or she will see an alert view managed by iOS, as shown in Figure 4-1. Privacy permissions are keyed by application identifier, meaning subsequent launches (or reinstalls) of your application will not prompt the user to select the permission level again. (Users can change their permission level at any time by selecting the Privacy option in the iOS Settings application.)

9781484211953_Fig04-01.jpg

Figure 4-1. Core Motion permission alert

As with HealthKit, you need to make sure the user’s device has the hardware you want to access. Additionally, you should check that the device is capable of producing the data you want to log. The CMPedometer class provides access to the pedometer on a user’s device. To check if step data is available, call the isStepCountingAvailable() public method.

In the workout screen, you had to prompt the user for HealthKit permission as soon as the screen loaded, in order to load the table with valid data. In the create screen, you should initiate the request for Core Motion permission when the user attempts to start a workout. This is initiated by pressing the Start Workout button, which calls toggleWorkout(). Depending on whether or not a workout is in progress, the method will call startWorkout() to begin accessing the pedometer or stopWorkout() to save progress. The flag for determining whether a workout is in progress is stored as a Boolean instance variable, called workoutActive. Listing 4-2 describes the toggleWorkout() method for the create workout view controller (CreateWorkoutViewController.swift) and adds the workoutActive flag as part of the class definition.

In Listing 4-2 I chose to wrap the logic of starting a workout in the startWorkout() method. This is where you will place your permission handling code. When you have established that your application has permission to use Core Motion, change the color and state of the Start Workout button to indicate that you have started the workout. Listing 4-3 provides the startWorkout() method.

Querying for Step Count

By this point, it should be no surprise that HealthKit and Core Motion share many design similarities. Just as you had to instantiate the HKHealthStore object to retrieve HealthKit data, you need to instantiate a CMPedometer object to access the pedometer on a user’s device. As shown in Listing 4-4, add a CMPedometer object to the CreateWorkoutViewController class and initialize it when you have established that the device has permission to use Core Motion.

Core Motion also implements concepts of queries to retrieve a set of data between two times and an observer query to retrieve updates to a data set. The CMPedometer method for retrieving steps between two times is queryPedometerDataFromDate(NSDate, toDate: NSDate, withHandler: CMPedometerHandler). This method takes two NSDate objects as parameters and executes a block when it has completed executing, which returns a CMPedometerData object and error. The CMPedometer object contains values including number of steps and distance traveled, calculated based on hardware events and metrics that iOS has observed about a user, including his stride length. As with earlier HealthKit queries, you should use the time the user started his workout as the start time and the time he ended his workout as the “end time.” This also specifies that you will need to perform the pedometer query in the stopWorkout() method. First, add an NSDate object to your class indicating the start time, and initialize it in the startWorkout() method, as shown in Listing 4-5.

Next, you need to implement your query logic in the stopWorkout() method. In this method, change the state of the Workout button back to start and attempt to query for step data after establishing that the pedometer is active (the object should be initialized). You will save the number of steps to HealthKit, but for now, display it in the progress label. Listing 4-6 provides the implementation for the stopWorkout() function.

To further improve the user experience, you should use an NSTimer to update the time label every second after the workout has started; this gives users the “clock” functionality that they expect from other workout devices, such as a watch. In Listing 4-7, I have added an NSTimer to the CreateWorkoutViewController class and initialized it in the startWorkout() method. I specify that the timer should repeat every second and call the updateTime() method when it fires.

The updateTime() method creates a string based on the number of seconds that have passed since the timer started and updates the timeLabel property. Listing 4-8 provides the updateTime() method. Once again, you can use the toString() method created earlier to make a human-readable string based on a NSTimeInterval value.

Finally, to stop the timer, call the invalidate() method on the timer instance variable, as shown in Listing 4-9. One of the design decisions that drives a repeating NSTimer is that in order to stop it, you must maintain a pointer to the object and explicitly call the invalidate() method to stop it from firing again. Maintaining pointers is tiresome, but it allows you to control multiple repeating timers.

Detecting Live Updates to Step Count

The startPedometerUpdatesFromDate(NSDate, withHandler:  CMPedometerHandler) method in the CMPedometer class allows you to query for real-time updates to the pedometer. This is not ideal for tracking the grand total of steps collected during a workout, as iOS controls the update frequency and it is not guaranteed to be timely. However, it is useful for the create workout screen, as you can use it to update the user interface.

Using startPedometerUpdatesFromDate() is much simpler than querying for a set of pedometer data; you simply need to provide a handler method and start date. Since the updates will be continuous while a workout is active, it makes sense to start the query when the user has started the workout. The wireframe for the RunTracker application specifies that a user can pause and resume a workout before saving it, so add another NSDate object to the class to specify the initial start time of the workout, as shown in Listing 4-10.

Having established this second NSDate object, you can now query for sample data using the initial start time at the end of the workout, as shown in Listing 4-11. Remember to update the total workout time and to check for any errors while performing your query.

Finally, you need to do some housekeeping when the workout has stopped. To prevent the pedometer from firing updates while the workout is paused or after you have exited the create screen, call the stopPedometerUpdates() method. Listing 4-12 describes the modified stopWorkout() method, which includes this call.

Detecting Activity Type

Another advantage of Core Motion is that you can detect the type of activity the user is engaged in, such as running, walking, or bicycling. This feature will be extremely useful in the RunTracker application, as you need to tag workouts with a type. The class for accessing activity status in Core Motion is CMActivityManager. To receive activity updates, call the startActivityUpdatesToQueue(NSOperationQueue, withHandler: CMMotionActivityHandler) method, specifying an operation queue (the main queue) and a completion handler that should execute when an update is received. In Listing 4-13, I have added an activity manager to the startWorkout() method.

Core Motion does not reveal activity types as human-readable strings, just like HealthKit. As a further complication, Core Motion does not provide an enum for storing the activity type. To check activity type, you must iterate through properties representing common values.

To create a workout in HealthKit, you must specify a workout type. Earlier, I saved the last known workout type to an instance variable named lastActivity, excluding stationary activities (users expect to see active workout types like running or walking). In Listing 4-14, I have modified the class definition once again to include the lastActivity property.

Saving Data to HealthKit

To complete the round-trip process for the fitness data you collected, you need to save it back into HealthKit. In the RunTracker application, you will save the steps you collected to HealthKit and you will compile these segments together as a single workout in HealthKit. To begin, you need to give the create workout screen access to HealthKit. As with the workout table, add an HKHealthStore property to the CreateWorkoutViewController class and include the HealthKit framework, as shown in Listing 4-15.

While it would be easy to create another HKHealthStore, it is wiser to share the one you created for the workout table. You can take advantage of segues to share the health store from the workout table with the create workout view. In the WorkoutTableViewController class, implement the prepareForSegue() method to detect when a segue has fired. As shown in Listing 4-16, after determining that it is a “CreateWorkoutSegue,” extract the destination view controller of the segue. You can set the healthManager property on this view controller to the health store from the workout table.

Now that you have initialized your healthStore property, you can perform save operations to save data. Remember that in the stopWorkout() method, after executing the step query, you displayed it in a label. To make the RunTracker application fully functional, you need to save it to HealthKit. The method for saving a piece of data in a health store is:

saveObject(HKObject, withCompletion: { (Bool, NSError?) -> Void in }). This method takes an HKObject as input and executes a completion handler when it has finished attempting to save the object.

For the input parameter, you need to convert step count to an HKQuantitySample. As shown in Listing 4-17, follow the same process you used earlier to look up the unit and type object for steps. Once you have these two parameters, you can create a new HKQuantitySample. For the quantity value, convert your step count from the pedometer to a double value.

For the save operation, you need to define a completion handler. Since the RunTracker application aggregates segments into a single workout, append the newly created HKQuantitySample into an array as shown in Listing 4-18.

In Listing 4-19, I have modified the class definition to include the sample array.

Creating a Workout Object in HealthKitAs mentioned in the design for the RunTracker application, the user presses the Done button to complete his workout and saves it to HealthKit. In the handler for the Done button, done(), you should create a new HKWorkout object and save it to HealthKit, similar to the way you saved HKQuantitySamples every time the user paused his workout.

To create a new workout, use the constructor HKWorkout(activityType: HKWorkoutActivityType, startDate: NSDate, endDate: NSDate). For the activity type, convert the last valid CMMotionActivity, stored in the lastActivity property to an HKWorkoutType. As shown in Listing 4-20, I have added this logic to the done() method by specifying an HKWorkoutType based on the activity’s type property.

The rest of the logic for saving a workout is relatively straightforward. As shown in Listing 4-21, you can now create an HKWorkout and save it using the health store’s saveObject() method. For the start time, use the initial start time when the user started the first segment of his workout.

To associate a set of samples to a workout, use the method:

addSamples(_:toWorkout:completion:)

which requires a set of samples, a workout, and a completion handler. As shown in Listing 4-22, use the sampleArray and newly created workout as your input, and exit the view controller when the operation has completed successfully.

Having implemented all of the changes in this chapter, the RunTracker app should now be complete and look like the screenshot in Figure 4-2.

9781484211953_Fig04-02.jpg

Figure 4-2. Completed RunTracker application

Summary

In this chapter, you learned how to use HealthKit and Core Motion to track a user’s fitness activity by building the RunTracker activity, which displayed a user’s past workouts, and allowed the user to create new ones. Through the process of building RunTracker, you learned about the similarities between the two frameworks, including how they require user permission and hardware checks before you can start using them. You also learned about queries, which allow you to poll for data on demand and receive updates when a data source has changed. Finally, you converted data from the pedometer into a data type that HealthKit could use and saved it back to HealthKit.

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

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