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
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")
}
}
}
}
I am using GCD in swift
like this :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
//all background task
dispatch_async(dispatch_get_main_queue()) {
self.second()
}
}
In this code second function is getting called before completing all background task that's why I am not able to take some data which I am using in second function. I want to second method after completing all background task. Can anyone tell me how to achieve this task?
***************In background I am taking healthkit data like******
let healthKitTypesToRead =
Set(
arrayLiteral: HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth)!,
HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBiologicalSex)!,
HKObjectType.workoutType()
)
let newCompletion: ((Bool, NSError?) -> Void) = {
(success, error) -> Void in
if !success {
print("You didn't allow HealthKit to access these write data types.\nThe error was:\n \(error!.description).")
return
}
else
{
let stepCount = 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 date = NSDate()
//let predicate = HKQuery.predicateForSamplesWithStartDate(date, endDate: NSDate(), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let stepCountQuery = HKSampleQuery(sampleType: stepCount!, predicate:.None, 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())
}
testClass.HK_stepCount = String(steps)
}
//completion(steps, error)
}
self.healthKitStore.executeQuery(stepCountQuery)
//EDIT.....
let tHeartRate = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let tHeartRateQuery = HKSampleQuery(sampleType: tHeartRate!, predicate:.None, limit: 0, sortDescriptors: nil) { query, results, error in
if results?.count > 0
{
var string:String = ""
for result in results as! [HKQuantitySample]
{
let HeartRate = result.quantity
string = "\(HeartRate)"
print(string)
}
testClass.HK_HeartRate = string
finalCompletion(Success: true)
}
}
self.healthKitStore.executeQuery(tHeartRateQuery)
}
}
healthKitStore.requestAuthorizationToShareTypes(healthKitTypesToWrite, readTypes: healthKitTypesToRead, completion: newCompletion)
I am not able to take value of step count, It get executed after calling second(method) called, plz suggest me what to do?
You can create a separate function to execute your task on other thread
func someFunction(finalCompletion: (Success: Bool)->()) {
let healthKitTypesToRead =
Set(
arrayLiteral: HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth)!,
HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBiologicalSex)!,
HKObjectType.workoutType()
)
let newCompletion: ((Bool, NSError?) -> Void) = {
(success, error) -> Void in
if !success {
print("You didn't allow HealthKit to access these write data types.\nThe error was:\n \(error!.description).")
return
}
else
{
let stepCount = 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 date = NSDate()
//let predicate = HKQuery.predicateForSamplesWithStartDate(date, endDate: NSDate(), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let stepCountQuery = HKSampleQuery(sampleType: stepCount!, predicate:.None, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
// Edit--
for result in results as! [HKQuantitySample]
{
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
// heartBeat += ....
}
testClass.HK_stepCount = String(steps)
finalCompletion(Success: true)
}
//completion(steps, error)
}
self.healthKitStore.executeQuery(stepCountQuery)
}
}
healthKitStore.requestAuthorizationToShareTypes(healthKitTypesToWrite, readTypes: healthKitTypesToRead, completion: newCompletion)
}
Another function?
I will edit this answer in some time to tell you about a better technique to deal with async request. In general you should have a separate singleton class for such background tasks. (RESTful API service class.. but for now you can use the below method)
func getHeartBeatInfo(finalCompletionHeart: (Success: Bool)->()) {
let tHeartRate = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let tHeartRateQuery = HKSampleQuery(sampleType: tHeartRate!, predicate:.None, limit: 0, sortDescriptors: nil) { query, results, error in
if results?.count > 0
{
var string:String = ""
for result in results as! [HKQuantitySample]
{
let HeartRate = result.quantity
string = "\(HeartRate)"
print(string)
}
testClass.HK_HeartRate = string
finalCompletionHeart(Success: true)
}
}
self.healthKitStore.executeQuery(tHeartRateQuery)
}
And then you can call this method like so:
override func viewDidLoad() {
someFunction( { (finalCompletion) in
if finalCompletion == true {
getHeartBeatInfo( { finalCompletionHeart in
if finalCompletionHeart == true {
self.second()
}
})
}
})
}
Well, the title really says it all, and I haven't been able to find an answer anywhere that works for me so I am turning to StackOverFlow. I am trying to get a users step count and assign that value to a UILabel. So here is some of my code (please note that this function is contained in another class as a result the label is not within the scope of this function):
func readTodayHealthData() -> Int {
var stepCount: Int = 0
func getStepsHealthData() {
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
})
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
return stepCount
}
//Set UILabel Value
//**This code is in my View Controller which is in a separate class as a result this label is NOT within the scope of this function.**
myLabel.text = String(readTodayHealthData)
Then when I run the app on an actual device I see the label text is zero, and I know for a fact that I have done some walking today :). So, I think the issue is that when I try to set the labels value the function hasn't fully finished executing.
I know this because when I use the delay function and wait for two seconds I end up getting a value, but if I don't wait then I get a value of zero.
So the main question is: How do I check when a function is completely finished executing?
The thing is that the operation you're using is async, then you need to handle properly, you have two options here:
Update the UILabel in the completionHandler inside your function getStepsHealthData in the main thread because you are going to update the UI, like in this way:
func getStepsHealthData() {
var stepCount: Int = 0
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
//Set UILabel Value
myLabel.text = String(stepCount)
})
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
And you don't need to return anything.
If you want to return the step counts from the function so you need to play a little with closures and modify your function like in the following way:
func getStepsHealthData(completion: (steps: Int) -> ()) {
var stepCount: Int = 0
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
completion(stepCount)
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
And then you can call it like in this way from outside:
self.getStepsHealthData() { (steps) -> Void in
dispatch_async(dispatch_get_main_queue(), {
//Set UILabel Value
myLabel.text = String(stepCount)
})
}
I hope this help you.
The completion handlers (which you're already using) are called when the data is available. readTodayHealthData() will return well before that happens.
You need to use the data within the scope of the completion handler. For example, you could rewrite your function like this:
func updateLabel() {
var stepCount: Int = 0
func getStepsHealthData() {
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
self.myLabel.text = "\(stepCount)"
})
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
}
This will update the label when the data has been returned.
You aren't actually trying to check if a function has finished executing though. That block that you pass to HKStatisticsQuery isn't really part of your original function. If you really want to prevent the myLabel.text= line from executing until after the block is called, you could use a semaphore, but that's a terrible solution to your actual problem. Why not have your block that's passed to HK update the label directly?
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
myLabel.text = "\(stepCount)"
})
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.
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.