I have a function that takes the total number of steps recorded by the device, saves it to a variable, and then gets the step data from each day, adding them to another variable until the two have the same value. I need this in order to for the app to know when to stop when it is saving the all time step data to an array.
However, the second half of this function does not execute, and I have no idea why. Here is the function:
// allTimeStepTotal and allTimeStepSum are doubles that are defined with a value of 0.0
func stepsAllTime(completion: (Double, NSError?) -> () ) {
// The type of data we are requesting
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
// Our search predicate which will fetch data from now until a day ago
let predicate = HKQuery.predicateForSamplesWithStartDate(NSDate.distantPast() as! NSDate, 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())
}
}
completion(steps, error)
self.allTimeStepsTotal += steps
println("Total:")
println(self.allTimeStepsTotal)
println("Sum:")
println(self.allTimeStepsSum)
}
self.healthKitStore.executeQuery(query)
println("Moving On")
var x = 1
while self.allTimeStepsTotal > self.allTimeStepsSum {
x += -1
// The type of data we are requesting
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
var daysAgo = -1 * x
var daysSince = (-1 * x) + 1
// Our search predicate which will fetch data from now until a day ago
let samplePredicate = HKQuery.predicateForSamplesWithStartDate(NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitDay, value: daysAgo, toDate: NSDate(), options: nil), endDate: NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitDay, value: daysSince, toDate: NSDate(), options: nil), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let stepQuery = HKSampleQuery(sampleType: sampleType, predicate: samplePredicate, 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())
}
}
completion(steps, error)
self.allTimeStepsSum += steps
println("New Sum:")
println(self.allTimeStepsSum)
}
self.healthKitStore.executeQuery(stepQuery)
}
And here is the call:
healthManager.stepsAllTime({Double, NSError in
println("All Done")
})
println("Finished executing stepsAllTime")
Can anyone tell me what I need to fix, or what went wrong?
Assuming that allTimeStepsTotal and allTimeStepsSum are initialized to 0.0, the second half of that function won't execute because the HKSampleQuery you've created executes asynchronously—that is, it calls the resultHandler at some time in the future after the the while loop in the second half of your function is evaluated. The condition self.allTimeStepsTotal > self.allTimeStepsSum will evaluate to false since both values are still 0.0, and the loop will not execute.
Related
There isn't a whole of sample code on the internet for querying for sleep data. The below code will return all sleep samples from a given day which if you look at the Apple Health App include "Asleep" samples from the Apple Watch which are sleep intervals, but there is also an "In Bed" sample from the iPhone which contains the total range from getting in bed to getting out of bed. How can I query HealthKit for only this In Bed sample?
func sleepTime() {
let healthStore = HKHealthStore()
// startDate and endDate are NSDate objects
// first, we define the object type we want
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
// You may want to use a predicate to filter the data... startDate and endDate are NSDate objects corresponding to the time range that you want to retrieve
//let predicate = HKQuery.predicateForSamplesWithStartDate(startDate,endDate: endDate ,options: .None)
// Get the recent data first
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// the block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit: 100000, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if error != nil {
// Handle the error in your app gracefully
return
}
if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {
let startDate = sample.startDate
let endDate = sample.endDate
print()
let sleepTimeForOneDay = sample.endDate.timeIntervalSince(sample.startDate)
}
}
}
}
}
HKCategorySample contains the value of type Int which is the enumeration value for the sample.
It has three values:
0 -> inBed
1 -> asleep
2 -> awake
So, the proposed change if you only want inBed data is:
if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {
if sample.value == 0 {
let startDate = sample.startDate
let endDate = sample.endDate
print()
let sleepTimeForOneDay = sample.endDate.timeIntervalSince(sample.startDate)
}
}
}
}
Better way is to go with switch case.
if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {
switch sample.value {
case 0:
// inBed, write logic here
print("inBed")
case 1:
// asleep, write logic here
print("asleep")
default:
// awake, write logic here
print("awake")
}
}
}
}
Let's say we have some steps data in healthkit which was auto detected by iphone and written to healthkit. And we have some steps data was enetred by user manually. Now I can differentiate between these steps using this code
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)
print(newDate)
let predicate = HKQuery.predicateForSamplesWithStartDate(newDate, endDate: NSDate(), options: .None) // Our search predicate which will fetch all steps taken today
let query = HKSampleQuery(sampleType: type!, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
//if result.metadata!["HKWasUserEntered"]! == 1{
if results?.count > 0
{
for result in results as! [HKQuantitySample]
{
print("Steps \(result.quantity.doubleValueForUnit(HKUnit.countUnit()))")
print()
// checking and truncating manually added steps
if result.metadata != nil {
}
else{
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
}
print(steps)
}
completion(steps, error)
//}
}
executeQuery(query)
But Lets say if some other app writes some some step's data to healthkit like some fitness app.
Now I am able to read steps with device icon(auto recorded), but
How can I read steps with UP icon as well ?
(UP) is and fitness application by JawBone.
I've written a function that retrieves max heart rate and average heart rate from HKHealthStore. Though both HKStatisticQuery() calls work, due to the fact that completion handlers are asynchronous, the return values (maxHRT, avgHRT) for the function are (0, 0).
What is an elegant way to wait for both completion handlers to return, before updating return values and exiting function?
Code:
func calcHeartRateStatistics() -> (Double, Double){
var maxHRT:Double = 0
var avgHRT:Double = 0
// First specify type of sample we need, i.e. Heart Rate Type
//
let sampleType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
// Create query predicate so result only returns objects within the period between Start Date
// and End Date
//
let predicate = HKQuery.predicateForSamplesWithStartDate(pvtStartDate, endDate: pvtEndDate, options: .None)
// Query for Maximum Heart Rate that occurred between Start Date and End Date
//
let queryMaxHeartRate = HKStatisticsQuery(quantityType: sampleType!,
quantitySamplePredicate: predicate,
options: .DiscreteMax)
{ [unowned self] (query, result, error) in
if let maxQuantity = result?.maximumQuantity() {
let maxHeartRate = maxQuantity.doubleValueForUnit(HKUnit(fromString: "count/min"))
maxHRT = round(maxHeartRate)
}
}
// Query for Average Heart Rate that occurred between Start Date and End Date
//
let queryAverageHeartRate = HKStatisticsQuery(quantityType: sampleType!,
quantitySamplePredicate: predicate,
options: .DiscreteAverage)
{ [unowned self] (query, result, error) in
if let averageQuantity = result?.averageQuantity(){
let avgHeartRate = averageQuantity.doubleValueForUnit(HKUnit(fromString: "count/min"))
avgHRT = round(avgHeartRate)
}
}
pvtHealthStore.executeQuery(queryMaxHeartRate)
pvtHealthStore.executeQuery(queryAverageHeartRate)
return (maxHRT, avgHRT)
} // calcHeartRateStatistics
I have a function that gathers step data, but I need a way to make it wait for its query to finish before it loops. I realize there are other questions about this problem, but I can't figure out how to fix it. The function is below:
func stepsAllTime(completion: (Double, NSError?) -> () ) {
var stopStart = true
while stopStart {
x += -1
// The type of data we are requesting
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
var daysAgo = x
var daysSince = x + 1
var daysSinceNow = -1 * daysAgo
checker = allTimeSteps.count
// Our search predicate which will fetch data from now until a day ago
let samplePredicate = HKQuery.predicateForSamplesWithStartDate(NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitDay, value: daysAgo, toDate: NSDate(), options: nil), endDate: NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitDay, value: daysSince, toDate: NSDate(), options: nil), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let stepQuery = HKSampleQuery(sampleType: sampleType, predicate: samplePredicate, 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())
}
}
completion(steps, error)
self.allTimeStepsSum += steps
self.allTimeSteps.append(steps)
println("New Sum:")
println(self.allTimeStepsSum)
println("Days Since Today:")
println(daysSinceNow)
if !(self.allTimeStepsTotal > self.allTimeStepsSum) {
stopStart = false
}
}
if allTimeStepsTotal > allTimeStepsSum {
self.healthKitStore.executeQuery(stepQuery)
}
}
}
How can this be done? Is there some sort of "On Complete" function in Swift?
I'm assuming that my comment was accurate.
You'd probably want to take a look at a recursive pattern, something like this:
import HealthKit
class Person {
var nbSteps: Double = 0
func fetchInfo() -> Void {
let sampleType = HKSampleType()
let samplePredicate: NSPredicate? = nil // Your predicate
let stepQuery = HKSampleQuery(
sampleType: sampleType,
predicate: samplePredicate,
limit: 0,
sortDescriptors: nil) { (sampleQuery, object, error) -> Void in
self.nbSteps += 1 // or the value you're looking to add
dispatch_async(dispatch_get_main_queue(), { () -> Void in // Dispatch in order to keep things clean
self.fetchInfo()
})
}
}
}
You can make the program to wait till the query finishes by implementing callbacks.
You can read more about it in the following blog post
http://www.charles-socrates-chandler.com/blog/2015/2/10/callbacks-in-swift
So I have this function, which gets and prints all the HealthKit step data from the past 24 hours, and saves it to an array:
func stepsInPastDay(completion: (Double, NSError?) -> () )
{
var dayStepData = [Double]()
for x in 1...24 {
// The type of data we are requesting
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
var hoursAgo = -1 * x
var hoursSince = (-1 * x) + 1
// Our search predicate which will fetch data from now until a day ago
let predicate = HKQuery.predicateForSamplesWithStartDate(NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitHour, value: hoursAgo, toDate: NSDate(), options: nil), endDate: NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitHour, value: hoursSince, toDate: NSDate(), options: nil), 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())
}
}
completion(steps, error)
dayStepData.append(steps)
if dayStepData.count > 23 {
for item in dayStepData {
println(item)
}
}
}
self.healthKitStore.executeQuery(query)
println(dayStepData.count)
}
println(dayStepData.count)
}
However, when I tried to access the array (dayStepData) in my AppDelegate file, with "HKManager.stepsInPastDay.dayStepData" (HKManager is the class), Xcode returns an error. Is there a way to get the array from my function?
This is OOP (Object-oriented-programmming) 101 stuff. You are saving your value to a local variable. Of course it isn't visible in your app delegate.
Put the function in a singleton class of some sort make the function return the array as the function result.
If you're putting app logic in your app delegate you're doing it wrong. Keep your app delegate small and lightweight. It should ONLY handle startup and other app delegate tasks. Put your app-specific logic in other modules.