Populate UITableView with Health data? - ios

I am trying to populate my tableview with data from Health app. The problem is that this data comes in different order every time I load the screen.
I have functions for getting the data from the Health app:
func readWalkrunDistance(startDate: NSDate) {
healthManager.readRunningAndWalkingDistance(startDate, endDate: selectedDate!) { (results, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
var dict: [String: Double] = [String: Double]()
dict["Walkrun Distance"] = results
dict["number"] = 13
if let result = results {
self.walkrunDistance = result
}
self.healthData.append(dict)
self.updateData()
})
}
}
func readBodyMass(startDate: NSDate) {
healthManager.readMedianBodyMass(startDate, endDate: selectedDate!) { (results, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
var dict: [String: Double] = [String: Double]()
dict["Body Mass"] = results
dict["number"] = 7
if let result = results {
self.bodyMass = result
}
self.healthData.append(dict)
self.updateData()
})
}
}
And another 10 functions like these for other Health data. The problem is that the data comes every time in different order, sometimes walkrunDistance is shown twice and etc. I think it is because of the async calls, but I don't know how to wait for these functions to get the data, then go to cellForRowAtIndexPath...
Here is my function for calling all the Health functions:
func readAllData() {
selectedDate = ProjectManager.sharedInstance.chosenDate
selectedDate = NSCalendar.currentCalendar().dateByAddingUnit(.Day, value: 1, toDate: selectedDate, options: [])
let startDate = NSCalendar.currentCalendar().dateByAddingUnit(.Day, value: -1, toDate: selectedDate, options: [])
self.readActiveEnergyBurned(startDate!)
self.readBasalEnergyBurned(startDate!)
self.readBloodAlcoholContent(startDate!)
self.readBloodPressureDiastolic(startDate!)
self.readBloodPressureSystolic(startDate!)
self.readBodyFatPercentage(startDate!)
self.readBodyMass(startDate!)
self.readDistanceCycling(startDate!)
self.readFlightsClimbed(startDate!)
self.readAverageHeartRate(startDate!)
self.readMinimumHeartRate(startDate!)
self.readMaximumHeartRate(startDate!)
self.readLeanBodyMass(startDate!)
self.readStepCount(startDate!)
self.readWorkouts(startDate!)
self.tableView.reloadData()
}
I want first to get the data, then load it into the tableview in the correct way.

You can try to use dispatch_group.
Here's link to tutorial:
http://www.raywenderlich.com/79150/grand-central-dispatch-tutorial-swift-part-2
Just add var group = dispatch_group_create() variable into your class.
Add dispatch_group_enter(group) into all of your functions and dispatch_group_leave(group) to the end of your async calls.
func readWalkrunDistance(startDate: NSDate) {
healthManager.readRunningAndWalkingDistance(startDate, endDate: selectedDate!) { (results, error) -> Void in
dispatch_group_enter(group)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
var dict: [String: Double] = [String: Double]()
dict["Walkrun Distance"] = results
dict["number"] = 13
if let result = results {
self.walkrunDistance = result
}
self.healthData.append(dict)
self.updateData()
dispatch_group_enter(group)
})
}
}
Add before self.tableView.reloadData() - dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
This will resolve your "async" problem. -reloadData will be called after all functions.

Related

Using a completion handler to return an array of items from a NASA API

This is my working code to fetch one item from NASA API I used the completion handler as presented on Apple Programming book.
class PhotoInfoController {
func fetchPhotoInfo(completion: #escaping (PhotoInfo?) -> Void) {
let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
let query: [String:String] = [
"api_key" : "DEMO_KEY"
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let photoInfo = try? jsonDecoder.decode(PhotoInfo.self, from: data) {
completion(photoInfo)
} else {
print("Not found or data is not sanitazed.")
completion(nil)
}
}
task.resume()
}
}
The problem I having a hard time to figuring it out is how you can return an array go items (PhotoInfo) via a completion handler. This is my code so far:
class PhotoInfoController {
func fetchPhotoInfo(completion: #escaping ([PhotoInfo]?) -> Void) {
let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
var photoInfoCollection: [PhotoInfo] = []
for i in 0 ... 1 {
let modifiedDate = Calendar.current.date(byAdding: .day,value: -i ,to: currentDate)!
let stringDate = formatter.string(from: modifiedDate)
let query: [String:String] = [
"api_key" : "DEMO_KEY",
"date" : stringDate
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) { (data,
response,
error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let photoInfo = try? jsonDecoder.decode(PhotoInfo.self, from: data) {
photoInfoCollection.append(photoInfo)
} else {
print("Data was not returned")
}
}
task.resume()
}
completion(photoInfoCollection)
}
}
Any ideas or guide will greatly appreciated Thanks!
Code Implemented after suggestions:
class PhotoInfoController {
private let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
private let currentDate = Date()
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
return formatter
}()
private let jsonDecoder = JSONDecoder()
func fethPhotoInfo(itemsToFetch: Int, completion: #escaping ([PhotoInfo]?) -> Void) {
var count = 0
var photoInfoCollection: [PhotoInfo] = []
for i in 0 ... itemsToFetch {
let modifiedDate = Calendar.current.date(byAdding: .day, value: -i, to: currentDate)!
let query: [String : String] = [
"api_key" : "DEMO_KEY",
"date" : dateFormatter.string(from: modifiedDate)
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if let data = data,
let photoInfo = try? self.jsonDecoder.decode(PhotoInfo.self, from: data) {
photoInfoCollection.append(photoInfo)
count += 1
if count == itemsToFetch {
completion(photoInfoCollection)
}
} else {
print("Data for \(self.dateFormatter.string(from: modifiedDate)) not made.")
}
}
task.resume()
}
}
}
Your code won't work as written.
You use a for loop to start 2 URLSession dataTask objects. Those tasks are async; the code that invokes the network request returns right away, before the request has even been sent.
Then outside your for loop you invoke your completion handler, before your network requests have even had a chance to be sent out. You will need a mechanism to keep track of the number of pending requests and invoke the completion handler when both requests have finished.
Consider this function that simulates what you are doing:
func createAsyncArray(itemCount: Int, completion: #escaping ([Int]) -> Void) {
var count = 0; //Keep track of the number of items we have created
var array = [Int]() //Create an empty results array
//Loop itemCount times.
for _ in 1...itemCount {
let delay = Double.random(in: 0.5...1.0)
//Delay a random time before creating a random number (to simulate an async network response)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
//Add a random number 1...10 to the array
array.append(Int.random(in: 1...10))
//Increment the number of results we have added to the array
count += 1
print("In loop, count = \(count)")
//If we have have added enough items to the array, invoke the completion handler.
if count == itemCount {
completion(array)
}
}
}
print("at this point in the code, count = \(count)")
}
You might call that code like this:
let itemCount = Int.random(in: 2...10)
print("\(itemCount) items")
createAsyncArray(itemCount: itemCount) { array in
for i in 0..<itemCount {
print("array[\(i)] = \(array[i])")
}
}
Sample output from that function might look like this:
9 items
at this point in the code, count = 0
In loop, count = 1
In loop, count = 2
In loop, count = 3
In loop, count = 4
In loop, count = 5
In loop, count = 6
In loop, count = 7
In loop, count = 8
In loop, count = 9
array[0] = 8
array[1] = 6
array[2] = 5
array[3] = 4
array[4] = 7
array[5] = 10
array[6] = 2
array[7] = 4
array[8] = 7
Note that the output displays "at this point in the code, count = 0" before any of the entries have been added to the array. That's because each call to DispatchQueue.main.asyncAfter() returns immediately, before the code inside the closure has been executed.
The function above uses a local variable count to keep track of how many items have been added to the array. Once the count reaches the desired number, the function invokes the completion handler.
You should use an approach like that in your code.
Edit:
You should be aware that your network requests may complete out of order. Your code submits itemsToFetch+1 different requests. You have no idea what order those requests will finish in, and it is very unlikely that the requests will complete in the order they are submitted. If your second request completes faster than the first, its closure will execute first.
You're complicating it for yourself with trying to do everything in one method. Imagine you have the fetchPhotoInfo function working (it actually works, so, good job so far):
struct PhotoInfo: Codable {
let copyright: String
}
class PhotoInfoController {
private let base = "https://api.nasa.gov/planetary/apod"
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
return formatter
}()
func fetchPhotoInfo(forDate date: Date, completion: #escaping (PhotoInfo?) -> Void) {
guard var components = URLComponents(string: base) else {
completion(nil)
return
}
components.queryItems = [
URLQueryItem(name: "api_key", value: "DEMO_KEY"),
URLQueryItem(name: "date", value: dateFormatter.string(from: date))
]
guard let url = components.url else {
completion(nil)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil)
return
}
let photoInfo = try? JSONDecoder().decode(PhotoInfo.self, from:data)
completion(photoInfo)
}
task.resume()
}
}
Your next goal is to fetch multiple photo info. Keep your class, keep your method and add another one which leverages what you already have. One way to achieve this is to use DispatchGroup:
func fetchPhotoInfo(forDates dates: [Date], completion: #escaping ([PhotoInfo]) -> Void) {
// Group of tasks
let taskGroup = DispatchGroup()
// Result of photos
var result: [PhotoInfo] = []
// For each date ...
dates.forEach {
// ... enter the group
taskGroup.enter()
// Fetch photo info
fetchPhotoInfo(forDate: $0) { photoInfo in
defer {
// Whenever the fetchPhotoInfo completion closure ends, leave
// the task group, but no sooner.
taskGroup.leave()
}
// If we've got the photo ...
if let photoInfo = photoInfo {
// ... add it to the result. We can safely add it here, because
// the fetchPhotoInfo completion block is called on the
// URLSession.shared.delegateQueue which has maxConcurrentOperationCount
// set to 1 by default. But you should be aware of this,
// do not rely on it and introduce some kind of synchronization.
result.append(photoInfo)
}
}
}
// At this point, we told the URLSession (via fetchPhotoInfo) that we'd like to
// execute data tasks. These tasks already started (or not, we don't know), but
// they didn't finish yet (most likely not, but we don't know either). That's
// the reason for our DispatchGroup. We have to wait for completion of all
// our asynchronous tasks.
taskGroup.notify(queue: .main) {
completion(result)
}
}
You can use it in this way:
let pic = PhotoInfoController()
pic.fetchPhotoInfo(forDate: Date()) { info in
print(String(describing: info))
}
pic.fetchPhotoInfo(forDates: [Date(), Date().addingTimeInterval(-24*60*60)]) { infos in
print(infos)
}
And the output is:
Optional(NASA.PhotoInfo(copyright: "Stephane Guisard"))
[NASA.PhotoInfo(copyright: "Stephane Guisard"), NASA.PhotoInfo(copyright: "Zixuan LinBeijing Normal U.")]
There's no error handling, you have to add it yourself.
Even if I did provide an answer to your question, I'm going to mark it as a duplicate of Wait until swift for loop with asynchronous network requests finishes executing. Your code to fetch single photo info works and you are just struggling to understand how to wait for multiple asynchronous tasks.

Is it possible to read Apple Watch goals (move, step and stand) from HealthKit?

Is it possible to read Apple Watch move goal from HealthKit?
I can retrieve the Move value by using the Quantity Identifier HKQuantityTypeIdentifier.activeEnergyBurned. I could not find a similar identifier for the move goal.
//Declared globally
var healthStore: HKHealthStore?
func prepareHealthKit() {
guard HKHealthStore.isHealthDataAvailable() else {
return
}
var readTypes = Set<HKObjectType>()
readTypes.insert(HKObjectType.activitySummaryType())
healthStore = HKHealthStore()
healthStore!.requestAuthorization(toShare: nil, read: readTypes) { (isSuccess, error) in
/*
Assuming you know the following steps:
1. Start workout session: i.e. "HKWorkoutSession"
2. Wait for delegate: i.e "workoutSession(_:didChangeTo:from:date:)"
3. Start Query for Activity Summary in the delegate:
i.e our "startQueryForActivitySummary()"
*/
}
}
func startQueryForActivitySummary() {
func createPredicate() -> NSPredicate? {
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents([.year, .month, .day],
from: Date())
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
return predicate
}
let queryPredicate = createPredicate()
let query = HKActivitySummaryQuery(predicate: queryPredicate) { (query, summaries, error) -> Void in
if let summaries = summaries {
for summary in summaries {
let activeEnergyBurned = summary.activeEnergyBurned.doubleValue(for: HKUnit.kilocalorie())
let activeEnergyBurnedGoal = summary.activeEnergyBurnedGoal.doubleValue(for: HKUnit.kilocalorie())
let activeEnergyBurnGoalPercent = round(activeEnergyBurned/activeEnergyBurnedGoal)
print(activeEnergyBurnGoalPercent)
}
}
}
healthStore?.execute(query)
}
References:
https://crunchybagel.com/accessing-activity-rings-data-from-healthkit
https://developer.apple.com/documentation/healthkit/hkactivitysummary
https://developer.apple.com/documentation/healthkit/hkactivitysummaryquery
I got the answer. The move goal is accessible from HKActivitySummary.
You should request permission to read HKActivitySummaryType:
let activitySummaryType = HKActivitySummaryType.activitySummaryType()
let readDataTypes: Set<HKObjectType> = [activitySummaryType]
healthStore.requestAuthorization(toShare: nil, read: readDataTypes, completion: myCompletionHandler)
Then use HKActivitySummaryQuery to read the summary information
let query = HKActivitySummaryQuery(predicate: myPredicate) { (query, summaries, error) -> Void in
if error != nil {
fatalError("*** Did not return a valid error object. ***")
}
if let activitySummaries = summaries {
for summary in activitySummaries {
print(summary.activeEnergyBurnedGoal)
//do something with the summary here...
}
}
}
healthStore.execute(query)
Other activity summary data that is accessible from HKActivitySummary is available here.

Execute a function after completing all background process in swift

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()
}
})
}
})
}

Using parse for preparing rows in WatchOS

In my app I'm using Parse SDK to get list of medicines and amount from the database and pass it through the iPhone to the watch. I implemented on the watch two separate sendMessages in WillActivate() :
let iNeedMedicine = ["Value": "Query"]
session.sendMessage(iNeedMedicine, replyHandler: { (content:[String : AnyObject]) -> Void in
if let medicines = content["medicines"] as? [String] {
print(medicines)
self.table.setNumberOfRows(medicines.count, withRowType: "tableRowController")
for (index, medicine) in medicines.enumerate() {
let row = self.table.rowControllerAtIndex(index) as? tableRowController
if let row1 = row {
row1.medicineLabel.setText(medicine)
}
}
}
}, errorHandler: { (error ) -> Void in
print("We got an error from our watch device : " + error.domain)
})
Second:
let iNeedAmount = ["Value" : "Amount"]
session.sendMessage(iNeedAmount, replyHandler: { (content:[String : AnyObject]) -> Void in
if let quantity = content["quantity"] as? [String] {
print(quantity)
self.table.setNumberOfRows(quantity.count, withRowType: "tableRowController")
for (index, quant) in quantity.enumerate() {
let row = self.table.rowControllerAtIndex(index) as? tableRowController
row!.amountLabel.setText(quant)
}
}
}, errorHandler: { (error ) -> Void in
print("We got an error from our watch device : " + error.domain)
})
What i get is this: Problem. Is it because of two different messages ?
To display the medicine and the amount in the same table you could do the following:
Create a property let medicines = [(String, String?)]()
When the medicines arrive populate that array with the medicines. So that after this medicines looks like this [("Medicine1", nil), ("Medicine2", nil),...]
When the quantities arrive iterate over medicines and add the quantities to the array, so that it looks like this after that: [("Medicine1", "Quantity1"), ("Medicine2", "Quantity2"),...]
Use the medicines array to populate your table. Create a method that reloads the table:
Like this:
func reloadTable() {
self.table.setNumberOfRows(medicines.count, withRowType: "tableRowController")
var rowIndex = 0
for item in medicines {
if let row = self.table.rowControllerAtIndex(rowIndex) as? tableRowController {
row.medicineLabel.setText(item.0)
if let quantity = item.1 {
row.quantityLabel.setText(quantity)
}
rowIndex++
}
}
}
Call reloadTable() whenever you receive a message with quantity or data.
This is just a raw example to explain the idea. You have to be careful to keep the medicines and the quantities in sync. Especially when you load more data when the user scrolls down.
To fill the data from the messages into your array you can define two functions:
func addMedicines(medicineNames: [String]) {
for name in medicineNames {
medicines.append((name, nil))
}
}
func addQuantities(quantities: [String]) {
guard medicines.count == quantities.count else { return }
for i in 0..<medicines.count {
medicines[i].1 = quantities[i]
}
}

How to Wait for Asynchronous Function Swift

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

Resources