iOS 8 HealthKit Santiapps Marcio Valenzuela
iOS 8 HealthKit

Here is the start of our WorkOut view controller:

import Foundation
import UIKit
import HealthKit

class WorkoutViewController: UITableViewController, UITextFieldDelegate {

@IBOutlet var numberOfLapsTextField: UITextField!
@IBOutlet var metersPerLapTextField: UITextField!
@IBOutlet var workoutDurationTextField: UITextField!
@IBOutlet var paceTextField: UITextField!
var numberOfLapsValue: NSString?
var metersPerLapValue: NSString?
var workoutDurationValue: NSString?
var userWeight: Double?
var  healthStore:HKHealthStore?
}

We import what we need, we subclass UITableViewController and add the Text Field delegate protocol.  Here I have created 4 labels for:

  • numberOfLaps
  • metersPerLap
  • workoutDuration
  • pace

These labels have an underlying variable for each.  The reason the first 3 are strings is because these are not health kit data per se.  These will be stored in CoreData.  However they really should be NSNumbers because it would be quite nice to store them and take advantage of CoreData’s ability to retrieve ordered data and statistical data in its fetches as well.

Finally we declare our health store property.

First let’s look at our lifecycle methods:

override func viewDidLoad() {

super.viewDidLoad()

self.fetchUsersWeight()

}

func textFieldShouldReturn (textField: UITextField) -> (ObjCBool) {

textField.resignFirstResponder()

if self.numberOfLapsTextField != nil && self.metersPerLapTextField != nil && self.workoutDurationTextField != nil {

}

return true;

}

In viewDidLoad we call a fetchUsersWeight method because we will need that to calculate calories burned.  Then we implement textFieldShouldReturn for each label.  Let’s take a look at that first method called:

func fetchUsersWeight() -> () {

var todayPredicate: NSPredicate = self.predicateForSamplesToday()

var weightType: HKQuantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)

self.healthStore?.aapl_mostRecentQuantitySampleOfType(weightType, predicate: todayPredicate, completion: { (weight, error) -> () in

if weight == nil {

NSLog("Sorry, weight is empty...")

return

}

if let someWeight = weight {

var weightUnit: HKUnit = HKUnit.poundUnit()

self.userWeight = weight!.doubleValueForUnit(weightUnit)

}

})

}

Once again we make a fetch using the same extension method and helper predicate method as before.  This exemplifies the use of extensions because we need to include the predicate method in this Workout class, not so the extension.  So go ahead and add the predicate method now:

func predicateForSamplesToday () -> (NSPredicate) {

let calendar: NSCalendar = NSCalendar.currentCalendar()

let now: NSDate = NSDate()

let startDate: NSDate = calendar.startOfDayForDate(now)

let endDate: NSDate = calendar.dateByAddingUnit(.CalendarUnitDay, value:1, toDate:startDate, options:nil)!

return HKQuery.predicateForSamplesWithStartDate(startDate, endDate:endDate, options:HKQueryOptions.StrictStartDate)

}

Ok so the user has 2 options here: Done or Cancel.  Cancel is easy:

@IBAction func cancel(sender: AnyObject) -> () {

self.navigationController?.popViewControllerAnimated(true)

}

Now let’s take a look at Done:

@IBAction func saveMyWorkout(sender: AnyObject) -> () {

//1.  Enter the values for your workout & capture

numberOfLapsValue = numberOfLapsTextField.text

metersPerLapValue = metersPerLapTextField.text

workoutDurationValue = workoutDurationTextField.text

//A - Need to fetch the user's weight in kgs from healthstore

var myWeight: Double = self.userWeight! * 0.453

//B - Need to convert time worked out into hours

//C - Need to select pace from some sort of switch

var pace: Double = (paceTextField!.text as NSString).doubleValue

//D - Throw away numberOfLaps = (numberOfLapsValue! as NSString).doubleValue & (metersPerLapValue! as NSString).doubleValue

var totalCaloriesBurnedByWorkout: Double =  ( (myWeight * pace) ) * ((workoutDurationValue! as NSString).doubleValue)/60

var totalJoulesBurnedByWorkout: Double = totalCaloriesBurnedByWorkout*4.184

//1.5 Set to EnergyVC before saving

let energyVCInstance: EnergyViewController = self.navigationController!.viewControllers[0] as EnergyViewController

energyVCInstance.activeEnergyBurnedValueLabel?.text = NSString(format:"%.2f",totalJoulesBurnedByWorkout)

energyVCInstance.activeEnergyBurned = totalJoulesBurnedByWorkout

energyVCInstance.refreshControl?.endRefreshing()

//2.  Dismiss and set values on EnergyVC such that they can be saved to the healthStore

//Save object to healthstore

// MUST DEFINE HKQUANTITY_TYPE

var quantityType: HKQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned)

//MUST DEFINE HKQUANTITY

var quantity: HKQuantity = HKQuantity(unit: HKUnit.jouleUnit(), doubleValue:totalJoulesBurnedByWorkout)

//DATE & METADATA

var now: NSDate = NSDate()

var metadata: NSDictionary = ["HKMetadataKey":"Swim Session"]

//WORKOUT TYPE?

var workoutType = HKWorkoutActivityType.Swimming

//This creates the object to SAVE

var energyBurnt: HKQuantitySample = HKQuantitySample(type: quantityType, quantity:quantity, startDate:now, endDate:now, metadata:metadata)

NSLog("Before saving to the healthstore...")

self.healthStore?.saveObject(energyBurnt, withCompletion: { (success, error) in

NSLog("Saving to the healthstore...")

if (error != nil) {

NSLog("The error is: \(error)")

}

dispatch_async(dispatch_get_main_queue(), {

if success {

// This was for updating a uitableview

// Alert User

let alertController = UIAlertController(title: "Success!", message: "Data Saved", preferredStyle: UIAlertControllerStyle.Alert)

alertController.addAction(UIAlertAction(title: "Confirm", style: UIAlertActionStyle.Default, handler: {action in

println("confirm was tapped")

//dismiss this vc

self.navigationController?.popViewControllerAnimated(true)

}))

self.presentViewController(alertController, animated: true, completion: nil)

} else {

NSLog("An error occured saving your workout burn. In your app, try to handle this gracefully. The error was: %@.", error)

abort()

}

})

})

}

This time I take you step by step, once again, inside the same method so as to drive the point home.  First we take all necessary data from labels to make our calculations.  We make our calculation and populate totalJoulesBurnedByWorkout.

Then we set the properties in our EnergyViewController from here before we return.

And before returning, we save our data to the health store, creating an identifier, then a quantity, then a date, then some metadata and finally calling saveObject.  In the saveObject method we return an alertController for success and pop our Workout view controller, because remember this is a navigation stack, not a modally presented view controller.  Else we log an error.

In the final part, we will add CoreData to our project and save laps and meters data into CoreData as well.

C ya!