Tutorial

Using Sleep Analysis in HealthKit with Swift


Today, Sleep Revolution is a fad and users are curious than ever to not only know about the the time they’ve been asleep but also to analyze the data gathered over a period to reveal their sleeping trends. Advancements in technology including hardwares, and mobiles in particular have brought this seemingly ever-growing subject into a whole new light.

Apple provides a cool way to communicate with user’s personal health information in a secure manner and store the information securely through the built-in Health app. Not only can you use HealthKit to build a fitness app, the framework also allows you access sleep analysis data.

In this tutorial, I will give you a quick introduction to the HealthKit framework, and demonstrate how you can build a simple app for sleep analysis.

Introduction

The HealthKit framework provides a structure to save data in an encrypted database called the HealthKit store. You can access this database using the HKHealthStore class. Both iPhone and Apple Watch have their own HealthKit store. Health data is synced between Apple Watch and iPhone; however, old data is periodically purged from Apple Watch to save space. HealthKit and the Health app are unavailable on iPad.

HealthKit is a powerful tool if you want to create an iOS or watchOS app based on health data. It is designed to manage data from a wide range of sources, automatically merging the data from different sources based on user’s preferences. Apps can also access the raw data for each source, and merge the data themselves. Not only for body measurements, fitness or nutrition, the data can also be used for sleep analysis.

For the rest of the article, I will show you how to make use of the HealthKit framework for saving and accessing sleep analysis data on iOS. The same methods are applicable for watchOS applications as well. Please note that the tutorial is written using Swift 2.0 and Xcode 7. So make sure you use Xcode 7 (or up) to follow the tutorial.

Before moving on, download the starter project and unzip it. I have already created the user interface with basic functionalities for you. When you run the starter project, you will see a timer UI that shows the time elapsed after you push the start button.

Working with the HealthKit Framework

Our aim with the application is to save sleep analysis information and retrieve the data using the Start & Stop buttons. To use HealthKit, you must begin by granting the HealthKit capabilities to your app bundle. In your app project, navigate to your current target -> capabilities and turn on HealthKit.

HealthKit-allow

Next, you will need to create an instance of HKHealthStore in the ViewController class using the following code:

Later we will use the HKHealthStore instance to access the HealthKit store.

As mentioned, HealthKit grants users control over their health data. So you have to first request users’ permission before you can gain access (write/read) to the Sleep Analysis data of the user. To do so, first import the built-in HealthKit framework and update the viewDidLoad method like this:

This code will prompt the user to allow or deny the requested permissions. With the completion block, you can handle success or error and get the final result. It is not necessary that the user grants your app all the requested permissions. You must handle the errors gracefully in your app.

But for testing purpose, you must have to select the “Allow” option to grant the app permission to access the health data on your device.

Health-App-Permission

Writing Sleep Analysis Data

First, how can we retrieve the sleep analysis data? According to Apple’s documentation, each sleep analysis sample can have only one value. To represent the user being both in bed and asleep, HealthKit uses two or more samples with overlapping times. By comparing the start and end times of these samples, apps can calculate a number of secondary statistics:

  • the amount of time it takes for the user to fall asleep
  • the percentage of time in bed that the user actually sleeps
  • the number of times the user wakes up while still in bed
  • the total amount of time spent both in bed and asleep
record_sleep_data

In brief, you follow the below approach to save sleep analysis data to the HealthKit store:

  1. We need to define two NSDate objects corresponding to the start time and the end time.
  2. We then create an instance of HKObjectType using HKCategoryTypeIdentifierSleepAnalysis.
  3. We need to create a new object of the HKCategorySample type. You normally use category samples to record sleep data. Individual samples represent time periods when the user is in bed or asleep. So we will create both an in-bed sample and an asleep sample with overlapping times.
  4. Finally, we’ll save the object using saveObject of HKHealthStore.
Editor’s note: For the type of the sample, you can look it up in HealthKit Constants Reference.

If you translate the above into Swift, here is the code snippet to save sleep analysis data for both in bed and asleep values. Please insert the method in the ViewController class:

This function can be called at the time we want to save the Sleep Analysis data to HealthKit.

Reading Sleep Analysis Data

To read the sleep analysis data, we will need to create a query. You should begin by defining the HKObjectType category to HKCategoryTypeIdentifierSleepAnalysis. You may also want to use a predicate to filter the retrieved data using startDate and endDate which are NSDate objects corresponding to the time range that you may want to retrieve. You will also need to create a sortDescriptor for sorting the retrieved queries to select the desired result.

Your code for retrieving sleep analysis data should look like this:

This code queries the HealthKit to get all Sleep Analysis data and then sorts it down to descending order. Then each query is printed with the startDate and endDate along with the type of value i.e. In Bed or Asleep. I have set the limit to 30 to retrieve the last 30 recorded samples. You can also use the predicate method to choose your custom start and end dates.

App Testing

For the demo application, I have used an NSTimer to show the time elapsed since you push the start button. The NSDate objects are created on the start and end buttons to save sleep analysis data as the elapsed time. In the stop action method, you can invoke the saveSleepAnalysis() and retrieveSleepAnalysis() methods to save and get the sleep data.

In your apps, you may want to change the NSDate objects to choose relevant start and end times (probably different) for saving the in bed and asleep values.

Once you’ve made the changes, you can run the demo app and start the timer. Let it run for a few minutes and then tap the Stop button. After that, open the Health app. You’ll find the sleep data in there.

sleep-analysis-test

Some Advices for HealthKit Apps

HealthKit is designed to provide a common platform for app developers to share and access user’s data easily and avoids any possible duplication or inconsistency in data. Apple review guidelines are very specific for apps using the HealthKit and requesting read/write permissions without a clear demonstration of their use might result in the app being rejected.

Apps that save fake or incorrect data to the Health App would also be rejected. This means, that you cannot be naïve with your algorithms for calculating different health values like sleep analysis in this tutorial. You should try using the inbuilt sensor data for reading and manipulating any parameters to avoid calculating false data.

For the complete Xcode project, you can get it here.

Tutorial
RESTful API Tutorial: How to Upload Files to a Server
macOS
Deploying Mac Apps Outside App Store: How to Remember User Intent for Folders
iOS
Using iAd to Display Banner Ad in Your App
  • Timg

    TimgTimg

    Author Reply

    where is the starter project source file?


    • Simon Ng

      Simon NgSimon Ng

      Author Reply

      Just updated the post with the download link of the starter project. Please take a look again.


  • Benoit PASQUIER

    Quite interesting article. It reminded me a lot of an HealthKit article from 2 years ago though : https://benoitpasquier.fr/sleep-healthkit/. It’s quite weird than this post have been posted just a week after the developer published his sample project on his Github too…


  • Flavia

    FlaviaFlavia

    Author Reply

    Hi, i tried writing this code with Xcode 9 using Swift 3.2. Using this exact tutorial, i had a lot of errors from Xcode that made me rewrite a lot of lines. I solved most of the errors, however on line 50 of the code it reads:
    “Code after ‘return’ will never be executed”

    Could you please advise me on the best way to resolve this issue?

    Here is the code:
    //
    // ViewController.swift
    // SleepAnalysis

    import UIKit
    import HealthKit

    let healthStore = HKHealthStore()
    class ViewController: UIViewController {
    @IBOutlet var displayTimeLabel: UILabel!

    var startTime = TimeInterval()
    var timer:Timer = Timer()
    var endTime: NSDate!
    var alarmTime: NSDate!

    func saveSleepAnalysis() {

    //1. startTime(alarmTime) and endTime are NSDate Objects//
    if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
    //we create a new object that we want to add into our Health app(This is our INBED object)//
    let object1 = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.inBed.rawValue, start: self.alarmTime as Date, end: self.endTime as Date)
    // Time to save the object//
    healthStore.save(object1, withCompletion: { (success, errpr) -> Void in

    if errpr != nil
    {
    return
    }

    if success {
    print(“My new data was saved in HealthKit”)

    } else {
    //something happened again//
    }
    })
    //This our ASLEEP object//
    let object2 = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.asleep.rawValue, start: self.alarmTime as Date, end: self.endTime as Date)
    //now we save our objects to our mainLibrary known as HealthStore
    healthStore.save(object2, withCompletion: { (success, error) -> Void in
    if error != nil {
    //Something went wrong//
    return

    if success {
    print(“My new data (2: Asleep data) was saved into HealthKit”)
    } else {
    //something happened again//
    }
    }
    }
    )}

    }

    func retrieveSleepAnalysis() {
    //first, define our object type that we watn again in BOOLEAN FORMAT//
    if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {

    //use sortDescriptor to get teh recent data first: so from MostRecentData to PastData//
    let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)

    //we create our query with a block completion to execute
    let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit:30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in

    if error != nil {
    //something happends//
    return
    }
    if let result = tmpResult {

    //then i want the computer to do something with my data//
    for item in result {
    if let sample = item as? HKCategorySample {
    let value = (sample.value == HKCategoryValueSleepAnalysis.inBed.rawValue) ? “InBed” : “Asleep”
    print(“Healthkit sleep: \(sample.startDate) \(sample.endDate) = value: \(value)”)
    }
    }
    }
    }

    //finally, we execute our query: Print out our output file //
    healthStore.execute(query)
    }
    }
    override func viewDidLoad() {
    super.viewDidLoad()

    let typestoRead = Set([
    HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
    ])

    let typestoShare = Set([
    HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
    ])
    healthStore.requestAuthorization(toShare: typestoShare, read: typestoRead) { (success, error) -> Void in
    if success == false {
    NSLog(” Display not allowed”)
    }
    }
    }

    func updateTime() {
    let currentTime = NSDate.timeIntervalSinceReferenceDate

    //Find the difference between current time and start time.
    var elapsedTime: TimeInterval = currentTime – startTime

    //calculate the minutes in elapsed time.
    let minutes = UInt8(elapsedTime / 60.0)
    elapsedTime -= (TimeInterval(minutes) * 60)

    //calculate the seconds in elapsed time.
    let seconds = UInt8(elapsedTime)
    elapsedTime -= TimeInterval(seconds)

    //find out the fraction of milliseconds to be displayed.
    let fraction = UInt8(elapsedTime * 100)

    //add the leading zero for minutes, seconds and millseconds and store them as string constants

    let strMinutes = String(format: “%02d”, minutes)
    let strSeconds = String(format: “%02d”, seconds)
    let strFraction = String(format: “%02d”, fraction)

    //concatenate minuets, seconds and milliseconds as assign it to the UILabel
    displayTimeLabel.text = “\(strMinutes):\(strSeconds):\(strFraction)”
    }

    func start(sender: AnyObject) {
    alarmTime = NSDate()
    if (!timer.isValid) {
    let Selector : Selector = #selector(ViewController.updateTime)
    timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: Selector, userInfo: nil, repeats: true)
    startTime = NSDate.timeIntervalSinceReferenceDate
    }

    }

    func stop(sender: AnyObject) {
    endTime = NSDate()
    saveSleepAnalysis()
    retrieveSleepAnalysis()
    timer.invalidate()
    }

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

    }


  • vd248

    vd248vd248

    Author Reply

    Are the bed time and asleep time always the same?

    Can anyone tell me how can we calculate the asleep time? Thank you!


Shares