Getting yesterdays steps from HealthKit
Asked Answered
P

2

6

I'm building an app for personal use, and I am currently stuck on how to accurately get yesterdays steps from the healthkit. And then from there, placing it into a variable (should be easy, I know).

I have a HealthKitManager class that calls the function from inside a view, and then appends that to a variable from that same view.

I have scoured most of the healthKit questions, and I get back data, but I don't think it is accurate data. My phone data from yesterday is 1442 steps, but it's returning 2665 steps. On top of that, when I try to put the data is a variable it prints out as 0.

HealthKitManagerClass

import Foundation
import HealthKit

class HealthKitManager {
let storage = HKHealthStore()

init()
{
    checkAuthorization()
}

func checkAuthorization() -> Bool
{
    // Default to assuming that we're authorized
    var isEnabled = true

    // Do we have access to HealthKit on this device?
    if HKHealthStore.isHealthDataAvailable()
    {
        // We have to request each data type explicitly
        let steps = NSSet(object: HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)!)

        // Now we can request authorization for step count data
        storage.requestAuthorizationToShareTypes(nil, readTypes: steps as? Set<HKObjectType>) { (success, error) -> Void in
            isEnabled = success
        }
    }
    else
    {
        isEnabled = false
    }

    return isEnabled
}

func yesterdaySteps(completion: (Double, NSError?) -> ())
{
    // The type of data we are requesting (this is redundant and could probably be an enumeration
    let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)

    // Our search predicate which will fetch data from now until a day ago
    // (Note, 1.day comes from an extension
    // You'll want to change that to your own NSDate

    let calendar = NSCalendar.currentCalendar()
    let yesterday = calendar.dateByAddingUnit(.Day, value: -1, toDate: NSDate(), options: [])

    //this is probably why my data is wrong
    let predicate = HKQuery.predicateForSamplesWithStartDate(yesterday, endDate: NSDate(), options: .None)

    // The actual HealthKit Query which will fetch all of the steps and sub them up for us.
    let query = HKSampleQuery(sampleType: type!, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
        var steps: Double = 0

        if results?.count > 0
        {
            for result in results as! [HKQuantitySample]
            {
                steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
            }
        }

        //I'm unsure if this is correct as well
        completion(steps, error)
        print("\(steps) STEPS FROM HEALTH KIT")
        //this adds the steps to my character (is this in the right place)
        Player.User.Gold.addSteps(Int(steps))
    }
    //not 100% on what this does, but I know it is necessary
    storage.executeQuery(query)
}}

ViewControllerClass

import UIKit
import Foundation

class UpdateViewController: UIViewController {

@IBOutlet var back: UIButton!

let HKM = HealthKitManager()
var stepsFromPhone = Double()


override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    back.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2))


    HKM.yesterdaySteps(){ steps, error in
       self.stepsFromPhone = steps
    }

    Player.User.Gold.addSteps(Int(stepsFromPhone))
    print(Player.User.Gold.getSteps(), "IN PLAYER")

}

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


}

The output from

print(Player.User.Gold.getSteps(), "IN PLAYER")

is

0 IN PLAYER

The output from

print("\(steps) STEPS FROM HEALTH KIT")

is

2665.0 STEPS FROM HEALTH KIT

so, basically my questions are:

  1. what NSDate() do I need for the whole of yesterday?
  2. how do I take the steps from the yesterdaySteps() and correctly place them into a variable in the UpdateViewController?

Thank you for any help!

Parabasis answered 23/9, 2016 at 5:48 Comment(11)
have you connected any thirdparty tracker with your phone ?Stalagmite
What is your actuall requirement ? Fir example if a user add steps to healthkit manually, do you want to read then as well or not ?Stalagmite
There are no third party trackers with my phone, and the requirement is just to read the steps from the phones health app. Don't need manual adding of steps, eitherParabasis
So you only want to get the steps which were auto detected by the iphone ?Stalagmite
essentially, yes! Later if I want to make it better I will try to use the fitbit API, but for now I need to wrap my head around the HealthKit aha!Parabasis
I have posted an answer please check that.Stalagmite
@UmairAfzal thank you! The steps are now being placed into the variable correctly! The only issue now is that labels holding the variables are not updating with the values in the view (I have to go back to another view before they are on the labels), which is weird because I am calling the function to put the steps into the variable first, then trying to update the labels, but it doesn't work. Would you have any ideas? But that is molehill compared the mountain you helped me solve, so thank you!Parabasis
Please accept my answer if it solved your current problem. And post a new question for anew problem. ThanksStalagmite
Let us continue this discussion in chat.Stalagmite
it's okay! I realized it was because getting the data was taking so long it was done after the labels were updated. I fixed it by updating the labels after a pause. Thank you much for your help!Parabasis
My pleasure, Please accept my answer :-)Stalagmite
S
8

This is the method I am using in my healthStore class

    func TodayTotalSteps(completion: (stepRetrieved: Double) -> Void) {

    let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting

    let calendar = NSCalendar.currentCalendar()
    let interval = NSDateComponents()
    interval.day = 1

    let anchorComponents = calendar.components([.Day , .Month , .Year], fromDate: NSDate())
    anchorComponents.hour = 0
    let anchorDate = calendar.dateFromComponents(anchorComponents)

    let stepsQuery = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: nil, options: .CumulativeSum, anchorDate: anchorDate!, intervalComponents: interval)

    stepsQuery.initialResultsHandler = {query, results, error in
        let endDate = NSDate()

        var steps = 0.0
        let startDate = calendar.dateByAddingUnit(.Day, value: 0, toDate: endDate, options: [])
        if let myResults = results{  myResults.enumerateStatisticsFromDate(startDate!, toDate: endDate) { statistics, stop in
            if let quantity = statistics.sumQuantity(){
                let date = statistics.startDate
                steps = quantity.doubleValueForUnit(HKUnit.countUnit())
               // print("\(date): steps = \(steps)")
            }

            completion(stepRetrieved: steps)
            }
        } else {

            completion(stepRetrieved: steps)
        }
    }
    executeQuery(stepsQuery)
}

and here is How I am using it

func getStepsData() {

   // I am sendng steps to my server thats why using this variable
    var stepsToSend = 0

        MyHealthStore.sharedHealthStore.todayManuallyAddedSteps({ (steps , error) in
            if error != nil{
                // handle error
            }
            else{
                // truncating manuall steps
                MyHealthStore.sharedHealthStore.TodayTotalSteps({ (stepRetrieved) in
                    stepsToSend =  Int(stepRetrieved - steps)
                })
            }
        })
}

and here is the function used above for manually added steps which we are truncating in order to get exact steps

    func todayManuallyAddedSteps(completion: (Double, NSError?) -> () )
{
    let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting

    let date = NSDate()
    let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
    let newDate = cal.startOfDayForDate(date)
    let predicate = HKQuery.predicateForSamplesWithStartDate(newDate, endDate: NSDate(), options: .None) // Our search predicate which will fetch all steps taken today

    // The actual HealthKit Query which will fetch all of the steps and add them up for us.

    let query = HKSampleQuery(sampleType: type!, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
        var steps: Double = 0

        if results?.count > 0
        {
            for result in results as! [HKQuantitySample]
            {

                // checking and adding manually added steps
                if result.sourceRevision.source.name == "Health" {
                    // these are manually added steps

                    steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
                }
                else{
                    // these are auto detected steps which we do not want from using HKSampleQuery
                }
            }
            completion(steps, error)
        } else {
            completion(steps, error)
        }
    }
    executeQuery(query)
}

I hope it helps. Let me know if you face any issue.

Stalagmite answered 23/9, 2016 at 6:39 Comment(2)
works perfect! Just a warning to others who may have the same issue: if you have labels connected to the variables being updated after the function where you are getting data, they may refresh before the query is finished, so it will look like it didn't update. Adding a NSTimer that updated the labels after 2 seconds worked for meParabasis
Using a timer is not as good (power/CPU use) as putting the update in the completion block for the query -- Healthkit queries are fundamentally asynchronous and you have to work with that.Polytonality
U
1

You can use HKStatisticsQuery

let quantityType = HKSampleType.quantityType(forIdentifier: .stepCount)!

let predicate = HKQuery.predicateForSamples(
  withStart: startDate,
  end: endDate,
  options: [.strictStartDate, .strictEndDate]
)

let query = HKStatisticsQuery(
  quantityType: quantityType,
  quantitySamplePredicate: predicate,
  options: .cumulativeSum) { (query, result, error) in
    guard let result = result, error == nil else {
      print("HeathService error \(String(describing: error))")
      return
    }

    callback(result)
}
Unwrap answered 17/10, 2018 at 8:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.