Saving HealthKit Data & Closures

iOS 8 HealthKit Santiapps Marcio Valenzuela
iOS 8 HealthKit

This code bit saves:

  healthKitStore.saveObject(bmiSample, withCompletion: { (success, error) -> Void in
    if( error != nil ) {
      println("Error saving BMI sample: \(error.localizedDescription)")
    } else {
      println("BMI sample saved successfully!")
    }
  })

The method signature is:

saveObject(object: HKObject!, withCompletion completion: ((Bool, NSError!) -> Void)!)

This method takes an HKObject which is bmiSample

and it takes a completion closure which itself takes a bool & error and returns void.

So in our method call, we pass in the bmiSample as the HKObject and for the success and error completion block we say:

if error is NOT nil then log that error’s description,

else log that the bmiSample was saved successfully.

 

 

This code bit reads:

// 2. Call the method to read the most recent weight sample
self.healthManager?.readMostRecentSample(sampleType, completion: { (mostRecentWeight, error) -> Void in
 
  if( error != nil )
  {
    println("Error reading weight from HealthKit Store: \(error.localizedDescription)")
    return;
  }
 
  var weightLocalizedString = self.kUnknownString;
  // 3. Format the weight to display it on the screen
  self.weight = mostRecentWeight as? HKQuantitySample;
  if let kilograms = self.weight?.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo)) {
    let weightFormatter = NSMassFormatter()
    weightFormatter.forPersonMassUse = true;
    weightLocalizedString = weightFormatter.stringFromKilograms(kilograms)
  }
 
  // 4. Update UI in the main thread
  dispatch_async(dispatch_get_main_queue(), { () -> Void in
    self.weightLabel.text = weightLocalizedString
    self.updateBMI()
 
  });
});

Here we are calling the .readMostRecentSample method which has this signature:

func readMostRecentSample(sampleType:HKSampleType , completion: ((HKSample!, NSError!) -> Void)!)

This method takes an HKSampleType which is sampleType

and it takes a completion closure which itself takes an HKSample & error and returns void.

So in our method call, we pass in the sampleType as the HKSample and for the success and error completion block we say:

if error is NOT nil then log that error’s description,

else get that mostRecentWeight as self.weight, format it and set it as the label’s text.

How does this method get the actual value from the HKHealthStore?  Inside itself, it executes an HKQuery which itself says this:

let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescriptor])
    { (sampleQuery, results, error ) -> Void in
 
      if let queryError = error {
        completion(nil,error)
        return;
      }
 
      // Get the first sample
      let mostRecentSample = results.first as? HKQuantitySample
 
      // Execute the completion closure
      if completion != nil {
        completion(mostRecentSample,nil)
      }
  }
  // 5. Execute the Query
  self.healthKitStore.executeQuery(sampleQuery)

Give me:

a.  sampleType

b.  a predicate, limit, sortDescriptors

c. and a completion closure which takes a sampleQuery, results & error

such that;

if error it NOT nil, set the readMostRecentSample method’s completion closure to nil,error

otherwise set the completion closure with the results and no error.

HealthKit for iOS8: Part 7

iOS 8 HealthKit Santiapps Marcio Valenzuela
iOS 8 HealthKit

4. CoreData for other non-health stats

You made it to the end!  Ok, so we are basically going to be adding another store to our app and reading and writing data to THAT store as well.

First let’s add a new tab and make it a UITableViewController as well.  It will have dynamically populated cells.

HealthKit for iOS8
HealthKit for iOS8

Now embed it!

HealthKit for iOS8
HealthKit for iOS8

 

Your final storyboard should look like this:

HealthKit for iOS8
HealthKit for iOS8

 

Add a new Swift class called Swimming Data and set that new UITableViewController scene to its class.  Make that class file look like this:

import Foundation

import UIKit

class SwimmingData: UITableViewController {

}

Now we must add CoreData.  To do this we need to create a CoreData stack and a xcdatamodeld file.  First thing is first, let’s add the xcdatamodeld file by New->File->CoreData->DataModel.  Name it SwimModel.  Create an entity called Swim and add the following attributes:

  • pace : Int16
  • date : Date
  • laps : Int16
  • meters : Double
  • totalTime : Double

Now with the xcdatamodeld file selected, go to Editor and select Create NSManagedObject subclass:

HealthKit for iOS8
HealthKit for iOS8

 

Make sure SwimModel is selected, click Next, make sure to select the Swim entity, click Next and you should get a Swim.swift class like this:

import Foundation
import CoreData
class Swim: NSManagedObject {

@NSManaged var date: NSDate
@NSManaged var laps: NSNumber
@NSManaged var totalTime: NSNumber
@NSManaged var meters: NSNumber
@NSManaged var pace: NSNumber
}

Perfect!  All you need now is your stack!  To do this, again create a New->File->Source->Swift File-> and name it CoreDataStack.  Now replace everything in there with this:

import CoreData

class CoreDataStack {

let context:NSManagedObjectContext
let psc:NSPersistentStoreCoordinator
let model:NSManagedObjectModel
let store:NSPersistentStore?

 

init() {

//1

let bundle = NSBundle.mainBundle()

let modelURL = bundle.URLForResource("SwimModel", withExtension:"momd")

model = NSManagedObjectModel(contentsOfURL: modelURL!)!

 

//2

psc = NSPersistentStoreCoordinator(managedObjectModel:model)

 

//3

context = NSManagedObjectContext()

context.persistentStoreCoordinator = psc

 

//4

let documentsURL = applicationDocumentsDirectory()

let storeURL = documentsURL.URLByAppendingPathComponent("SwimFit4")

 

let options = [NSMigratePersistentStoresAutomaticallyOption: true]

 

var error: NSError? = nil

store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error:&error)

 

if store == nil {

println("Error adding persistent store: \(error)")

abort()

}

}

 

func saveContext() {

var error: NSError? = nil

if context.hasChanges && !context.save(&error) {

println("Could not save: \(error), \(error?.userInfo)")

}

}

 

func applicationDocumentsDirectory() -> NSURL {

let fileManager = NSFileManager.defaultManager()

 

let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as [NSURL]

return urls[0]

}

}

Now since this is not a CoreData tutorial, I will not go into the details, but every CoreData project needs a MOM, MOC and PSC.  That is what we initialize here.

Now we can begin writing to our MOC and PSC.  To test run it, let’s hardcode a value.  Go to the SwimmingData Class and first give it an import CoreData at the top.  Now declare a property for your stack inside your class of course:

lazy var coreDataStack = CoreDataStack()

var workouts = NSMutableArray()

We are creating a CoreDataStack instance and we are creating a mutable array.

Then give it a viewDidLoad method like this:

 

override func viewDidLoad() {

super.viewDidLoad()

//Create Sample Swim object

var description = NSEntityDescription.entityForName("Swim", inManagedObjectContext:coreDataStack.context)

var sampleSwim = Swim(entity:description!, insertIntoManagedObjectContext:coreDataStack.context)

sampleSwim.laps = 24

sampleSwim.meters = 50

sampleSwim.totalTime = 40

sampleSwim.pace = 6

sampleSwim.date = NSDate()

coreDataStack.saveContext()

 

//Add object to array

self.workouts.addObject(sampleSwim)

 

//Refresh UI

self.tableView.reloadData()

}

Now add an identifier for the cell like so:

let JournalViewControllerTableViewCellReuseIdentifier: NSString = "Cell"

as a property at the top of the class.

So of course don’t forget to set the identifier in your storyboard scene.  Finally, implement both datasource methods:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return self.workouts.count

}

override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {

 

let cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath!) as UITableViewCell

 

var myWorkout: Swim = self.workouts[indexPath!.row] as Swim

let dateFormatter = NSDateFormatter()

dateFormatter.dateFormat = "yyyy'-'MM'-'dd HH':'mm':'ss"

let date = dateFormatter.stringFromDate(myWorkout.date as NSDate)

println(date)

cell.textLabel.text = date

 

cell.detailTextLabel!.text = myWorkout.totalTime.stringValue

 

return cell;

}

And don’t forget to set your cell type to Right Detail in your storyboard.  If you Build & Run and switch to the newly created tab, you might get a crash saying:

Unable to load class Swim …

This is because you need to fully qualify the class name in CoreData, so select your xcdatamodeld file and with your Swim entity selected, make sure to append the Class name in the inspector on the right like so:

HealthKit for iOS8
HealthKit for iOS8

Basically you need to ensure that you append your project name to the Class name field.

Now run your app and go over to the Workouts tab and see your hardcoded workout in the tableview.

Before we move on, let’s take a few minutes to work on some details.  While this provides the info required by the user, it would be nice to polish it up a bit.  First, we should add the letters “mins” to the totalTime displayed in the cell.  Second, it would be nice to format the date a little more such that its more human readable.  So go back to your cellForRowAtIndexPath and make the following changes:

override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {

let cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath!) as UITableViewCell

var myWorkout: Swim = self.workouts[indexPath!.row] as Swim

var formatString = NSDateFormatter.dateFormatFromTemplate("EdMMM", options: 0, locale: NSLocale.currentLocale())

let dateFormatter = NSDateFormatter()

dateFormatter.dateFormat = formatString

let date = dateFormatter.stringFromDate(myWorkout.date as NSDate)

println(date)

cell.textLabel.text = date

cell.detailTextLabel!.text = myWorkout.totalTime.stringValue + " mins"

return cell;

}

There, now the user has a little mode detailed info of the data displayed.  We could go on and modify the cell to hold more data or even be selectable such that it would segue into a detail view controller to display all the info.

WRITING TO CORE DATA

Now all that is left to do is actually, remove that viewDidLoad code that writes to CoreData and instead, write to CoreData from our Workout view controller.  So back in WorkoutViewController, first import CoreData at the top, then add this property:

lazy var coreDataStack = CoreDataStack()

and finally, in the saveMyWorkout method, after we calculate our joules burned, or before, it doesn’t matter, add this code:

//E - Perhaps just store laps and meters per lap = total metes in some extra field within the SwimFit app to display it.

var description = NSEntityDescription.entityForName("Swim", inManagedObjectContext:coreDataStack.context)

var sampleSwim = Swim(entity:description!, insertIntoManagedObjectContext:coreDataStack.context)

var numberFormatter = NSNumberFormatter()

var nolaps:NSNumber? = numberFormatter.numberFromString(numberOfLapsValue!)

if let nolaps = nolaps {

sampleSwim.laps = Int(nolaps)

}

var nometers:NSNumber? = numberFormatter.numberFromString(metersPerLapValue!)

if let nometers = nometers {

sampleSwim.meters = Double(nometers)

}

var totime:NSNumber? = numberFormatter.numberFromString(workoutDurationValue!)

if let totime = totime {

sampleSwim.meters = Double(totime)

}

sampleSwim.pace = pace

sampleSwim.date = NSDate()

coreDataStack.saveContext()

This will save that other data, which is not HealthKit or health store data, into CoreData for later use.  Now let’s just go modify our SwimmingData view controller to make it fetch.

You already added a CoreDataStack variable to your SwimmingData view controller, so just add a fetchRequest var like this, right below the CoreDataStack var:

var coreDataStack: CoreDataStack!

var fetchRequest: NSFetchRequest!

Now in viewDidLoad add this neat code:

fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")

This is a stored fetch request and to use it you must head over to the xcdatamodeld.file and create a new FetchRequest, leave its name as FetchRequest and now from the editor leave Swim as the selected entity to fetch from.  Now go back to SwimmingData and add this method:

//MARK - Helper methods

func fetchAndReload(){

var error: NSError?

let results = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [Swim]?

if let fetchedResults = results {

workouts = fetchedResults.copy() as NSMutableArray

} else {

println("Could not fetch \(error), \(error!.userInfo)")

}

tableView.reloadData()

}

And now call this method from viewDidLoad.  This will load your fetched data from CoreData into your tableview.

This will fetch the items in the order they were inserted, but you can also add a sort descriptor.

Add this lazy property at the top of your SwimmingData class:

lazy var dateSortDescriptor: NSSortDescriptor = {

var sd = NSSortDescriptor(key: "Swim.laps",

ascending: true) return sd
}()

Then in the viewDidLoad add this as a property of your fetchRequest:

fetchRequest.sortDescriptors =[dateSortDescriptors]

 NSFETCHEDRESULTSCONTROLLER OPTION

Alternatively you can also use NSFetchedResultsController.  NSFRC is a neat object that is created specifically for fetching and manipulating data from a CoreData query.  Its special in many respects but mainly because it works nicely with table views.  It can store information about table structure and can allow for interaction between its data and the tableview at the same time.  So add this property to the top of your class:

var fetchedResultsController : NSFetchedResultsController!

In your viewDidLoad:

//1
let fetchRequest = NSFetchRequest(entityName: "Swim")
let sortDescriptor = NSSortDescriptor(key: "totalTime", ascending: false) fetchRequest.sortDescriptors = [sortDescriptor] 
//2 fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil) 
//3 var error: NSError? = nil 
if (!fetchedResultsController.performFetch(&error)) { 
println("Error: \(error?.localizedDescription)") } 

Now replace your datasource methods with:

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController.sections!.count
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}

Finally, the way you access data to populate your cell in cellForRowAtIndexPath is:

let mySwim = fetchedResultsController.objectAtIndexPath(indexPath) as Swim

cell.textLabel.text = date
cell.detailLabel.text = mySwim.totalTime.stringValue + " mins"

}

where date would be the formatted value of course.

Well it has been a long journey.  I hope you learned enough about Healthkit to feel comfortable enough to start working on your own app.

Have a good one!

Implicitly Unwrapped Optionals

As an ObjC programmer, I despised the introduction of Optionals in Swift.

However, I jumped on the Swift bandwagon on Day 1.

So I understood the basic concepts:

  1. If a value may at some point not have a value, declare it as optional.
  2. If a value has been declared optional, you must test its contents.
    1. You can test with if == nil
    2. You can if let test
    3. Or you can force unwrap

I also read about implicitly unwrapped optionals but I confused them with force-unwrapping.  It also threw me off about when and what to use them for.

Recently I’ve been working on an app that involves a dance between a data holder (AppDelegate), a view (GraphView) and a view controller (GraphViewController).  Plus there is a data setter (Swimming) which kinda complicates things.  These view controllers reside in tabs in the app.  And the functionality works kinda like this:

Swift : Implicitly Unwrapped Optionals iOS8
Swift : Implicitly Unwrapped Optionals iOS8

At the start of the app, the AppDelegate creates an array of data to be plotted (graphArray).  This is needed such that if the user then automatically jumps to the GraphViewController, that array will have a value.

NOTE:True, this could also happen in the GraphViewController’s initWithCoder.  But since Im using the AppDelegate as the data holder, I decided to declare and assign it here.

The user then has the option to go to the SwimmingVC tab or the GraphVC tab.

  • If the user goes straight to the GraphVC tab, it would require a set of values to plot in the graph.  This placeholder data was set in the AppDelegate.
  • If the user goes to the SwimmingVC tab, the real data is fetched from the database and used to set the AppDelegate’s graphArray property.  Essentially this overrides the placeholder data set at the start.

So no matter if the user went straight to the GraphVC or if he stopped by the SwimmingVC to fetch actual data, the GraphView would have a value.

The issue was that I had to define a property in the GraphView class to hold the data.  Again, not strictly following MVC because the View is using a local copy of the data, but that’s what was going on.

So I had to set the GraphView’s graphPoints property to: var graphPoints!

This means that this variable WILL have a value and can be implicitly unwrapped for the remainder of the class code (GraphView), instead of me having to go thru all the code and if/let test each use of self.graphPoints.

Nice!