Data from HealthKit is initially missing, then comes in when I try again - how to fix? - ios

I have an iOS app that I'm developing in Swift, and as part of the app it gathers the step count for the current day.
The first time I run the app, the count is "0", but if I click a button in the interface to re-run this function that queries HK, then the correct number appears.
I am guessing this is because HK needs some time to gather the data, or something, but I'm not sure how to fix it. Maybe HK can fire an event when the data is ready, and then I can update the UI?
Here's the routine that gathers the data from HK. This function executes immediately when the app start (and it shows "0 steps today"), and then as I describe above, I can tap a button to execute the function again (and then I get the right number).
func queryStepsSum() {
// prepare HK for the data
let endDate = NSDate() // right now
let startDate = NSCalendar.currentCalendar().dateBySettingHour(0, minute: 0, second: 0, ofDate: endDate, options: NSCalendarOptions())
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
let sumOption = HKStatisticsOptions.CumulativeSum
let statisticsSumQuery = HKStatisticsQuery( quantityType: self.stepsCount!, quantitySamplePredicate: predicate,
options: sumOption)
{ [unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
self.numberOfSteps = Int(sumQuantity.doubleValueForUnit(HKUnit.countUnit()))
}
}
// run the HK query
self.healthStore?.executeQuery(statisticsSumQuery)
// update the UI with the result
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.stepsLabel.text = "\(self.numberOfSteps) steps today";
});
}

Your code doesn't wait for the query to finish before displaying the result (the query happens asynchronously). You should update your UI from the query's result handler, like this:
let statisticsSumQuery = HKStatisticsQuery( quantityType: self.stepsCount!, quantitySamplePredicate: predicate,
options: sumOption)
{ [unowned self] (query, result, error) in
dispatch_async(dispatch_get_main_queue()) {
if let sumQuantity = result?.sumQuantity() {
self.numberOfSteps = Int(sumQuantity.doubleValueForUnit(HKUnit.countUnit()))
self.stepsLabel.text = "\(self.numberOfSteps) steps today";
}
}
}
Also note that I've included a dispatch back to the main thread since the query results handler runs on a background thread and it's not safe to manipulate UI in that context.

Are you calling your function in viewDidLoad? You might try calling it in viewDidAppear instead.
If that doesn't work maybe there is some sort of delegate method that can update you when the data is ready like you said. I've never worked with HealthKit but I'll check to see if I can find anything.
EDIT:
This might set the UI component off the background thread.
Try:
var numberOfSteps: Int! {
didSet {
self.stepsLabel.text = "\(self.numberOfSteps) steps today"
}
}
That should get rid of the error that you're updating the UI on the background thread. Also make sure to remove your call in the closure that I told you to put.

Related

Setting up an HKAnchoredObjectQuery so that I only receive updates since the last time I queried?

I'm trying to set up an HKAnchoredObjectQuery that will only deliver results from the last time I made this query, but I can't get my head around the logic in setting up my HKQueryAnchor and how I persist it? In Apple's sample code they do not show the initial declaration for the HKQueryAnchor. Do I need to store locally the date of the last sample I downloaded and construct an anchor from that date? This code below returns every sample in HealthKit.
func updateWorkouts(completionHandler: #escaping () -> Void) {
var anchor: HKQueryAnchor?
let sampleType = HKObjectType.workoutType()
let workoutPredicate = HKQuery.predicateForWorkouts(with: .hockey)
let sourcePredicate = HKQuery.predicateForObjects(from: HKSource.default()) //limit query to only this app
let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [workoutPredicate, sourcePredicate])
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: compound, anchor: anchor, limit: HKObjectQueryNoLimit) { [unowned self] query, newSamples, deletedSamples, newAnchor, error in
self.handleNewWorkouts(newWorkoutsAsSamples: newSamples!, deleted: deletedSamples!)
anchor = newAnchor
completionHandler()
}
healthStore.execute(anchoredQuery)
}
When initializing an HKAnchoredObjectQuery, you are expected to either provide nil or an anchor object that you received from a query that you executed previously. You cannot directly construct an HKQueryAnchor yourself. To persist an anchor between application launches, you can encode it in persistent storage using NSKeyedArchiver. It is common to store the resulting encoded NSData in NSUserDefaults.

Asynchronous function not updating variable

var endLat: Double = 0.0
var endLong: Double = 0.0
func forwardGeocodingStarting(address: String) {
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print(error)
return
}
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
dispatch_async(dispatch_get_main_queue()) {
startLat = (coordinate?.latitude)!
startLong = (coordinate?.longitude)!
print("\(startLat) is lat and \(startLong) is long")
}
})
}
Hello, here is a geocoding function that I created, that simply takes an address and returns the address's coordinates. My problem is, that at the end of the code section, when I do print(endLat) it prints out 0, however when I do it in the dispatch_async in the function, it comes out to the proper latitude.
I realize this is an asynchronous function, which is why I tried to use the dispatch_async to update the variable instantly.
My question is, how can I have these variables update instantly? Thanks in advance.
You've got a little confused with what you mean by "Asynchronous function not updating variable". forwardGeocodingStarting(_:) is NOT an Async function. geocodeAddressString is an async function designed by the API to allow you to use the following values you get after the operation is done within that function only. Hence you keep getting 0 as the it doesn't wait till the operation is done completely to display the value. dispatch_async(dispatch_get_main_queue) is used only to update UI on the Main thread. You've to use a completion handler in your function like so:
typealias theDouble = (Double, Double) -> ()
func forwardGeocodingStarting(address: String, completion: theDouble){
//your code
let aVar: Double!
let bVar: Double!
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
aVar = (coordinate?.latitude)!
bVar = (coordinate?.longitude)!
completion(aVar, bVar)
}
Now when you call your function, update the variables startLat and startLong like so:
forwardGeocodingStarting(<your string address>) { firstVar, secondVar in
startLat = firstVar
startLong = secondVar
print("\(startLat) is lat and \(startLong) is long") //You will get correct values
}
You cannot have these variables update instantaneously, as you are calling an asynchronous function. Thats an obvious contradiction :-)
That you dispatch back to the main queue is fine, but that doesn't change the asynchronous behaviour. In fact your are even issuing yet another dispatch_async ... (meaning the body of that code will get run at some later point in time)
You could wait for the results to be available, e.g. using a dispatch group, but quite likely that is not what you want here.
I assume you want to use the values in some view controller? In this case you probably fill in some instance variables as the results arrive and update the UI state once both values are available.

Why HKSample array always have 1 value for a HKAnchoredObjectQuery with no limits,no predicate, no anchor?

I am trying to understand how HKAnchoredObjectQuery works. Once the workout started and workout session state changes to running, I call the following function to execute the query and get the Heart Beat Value.
func createHeartRateStreamingQuery() {
guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else { return nil }
var heartRateQuery : HKAnchoredObjectQuery? = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
}
heartRateQuery!.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
{
//Samples only have 1 entry which is the most recent reading.
}
}
self.healthStore.executeQuery(heartRateQuery!)
}
HeartRateQuery's update handler is called every 2 to 3 seconds and samples variable in the completion handler is having only 1 reading of the Heart Rate which is the most current reading. Shouldn't it have all the readings of Heart Rate since the workout started since I have not set any limits, predicate or anchor on the query?
The behavior you are seeing is expected. The updateHandler is only called with samples that are new since the handler was last invoked. If you want to keep track of the samples recorded during the workout then you should add them to an array each time the handler is called.
Note that because you are not using a predicate, the initial results block will include all heart rate samples that are currently available in HealthKit, not just the samples recorded during the workout session. You should probably constrain the query with a date predicate to only get the samples you are interested in.

Convert asynchronously large no of GPS data to city/country

Background: I have a list of contacts, which are retrieved on an asynchronous queue from a cloud based database. Once done, I dispatch back to the main queue and show these contacts in a TableView.
Besides names and other details, each contact object has GPS coordinate properties (latitude and longitude). I want to use these GPS coordinates to retrieve the name of the city and country of each contact and update the TableView showing that information in the local language of the device the user has.
Problem: The problem I am trying to overcome is that I have a few hundred contacts. Initially, I used concurrent queues to get the city/country strings. But I paused the app in XCode and realised the disaster of 300+ threads created. So I changed the code running each lookup of the city/country on the same serial queue.
The issue now is that only for about 50 of the contacts the data is updated. I do not know why and not sure even if the queue is serial. Debugging shows there are still 100+ “serial queues” created. I expected one at the time. What am I doing wrong? Thanks
The code for the class, in which I have my TableView is as follows:
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
// get or update the contactsList
activeUser.contactsList.getAllContacts()
// once we have the contacts we go back to the main queue
dispatch_async(dispatch_get_main_queue()) {
// and refresh tableview to show the contacts
self.tableView.reloadData()
// now we refresh the locations of the contacts
for index in 0...activeUser.contactsList.listOfContacts.count
{
// set local variables for lat and long
let lat = activeUser.contactsList.listOfContacts[index].latitude
let long = activeUser.contactsList.listOfContacts[index].longitude
// call location service method with completion handler
MyLocationServices().updateLocationToLocalLanguage(lat, longitude: long, completionHandler:
{ (city, country) -> () in
// update contact's details
activeUser.contactsList.listOfContacts[index].city = city
activeUser.contactsList.listOfContacts[index].country = country
// refresh each time the table to show updated contact data
self.tableView.reloadData()
})
}
}
The method within the MyLocationServices class looks as follows:
func updateLocationToLocalLanguageDispatched(latitude: String, longitude: String, completionHandler: (city: String, country: String) -> ())
{
let serialQ = dispatch_queue_create("AddressUpdateQ", DISPATCH_QUEUE_SERIAL)
dispatch_async(serialQ)
{
var cityString = "NA"
var countryString = "NA"
let group = dispatch_group_create()
dispatch_group_enter(group)
let location = CLLocation(latitude: Double(latitude)!, longitude: Double(longitude)!)
self.geocoder.reverseGeocodeLocation(location)
{
(placemarks, error) -> Void in
if let placemarks = (placemarks as [CLPlacemark]!) where placemarks.count > 0
{
let placemark = placemarks[0]
if ((placemark.addressDictionary!["City"]) as? String != nil) { cityString = ((placemark.addressDictionary!["City"]) as? String)! }
if ((placemark.addressDictionary!["Country"]) as? String != nil) { countryString = ((placemark.addressDictionary!["Country"]) as? String)! }
}
dispatch_group_leave(group)
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
dispatch_async(dispatch_get_main_queue())
{
completionHandler(city: cityString, country: countryString)
}
}
}
I have solved the issue and documenting hoping it is helpful in case others face similar problems.
(1) Serial Queue: I have realised serial queue initiation should not happen within the method, but enclose the for-loop. Thus the following code should be removed from the method and put around the for-loop. But this does not solve the fact that I only get about 50 results and nothing for the remaining GPS data of the contacts.
let serialQ = dispatch_queue_create("AddressUpdateQ", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQ)
{ [for index....] }
(2) Location: The second issue is related to Apple's Geocoder, which happens online. Officially, the number of requests are limited. The documentation states that
one "should not send more than one geocoding request per minute".
Applications should be conscious of how they use geocoding. Geocoding
requests are rate-limited for each app, so making too many requests in
a short period of time may cause some of the requests to fail.
Checking the error code I get, it is Error Domain=kCLErrorDomain Code=2. It seems the service is denied and I cannot make further requests.
(3) Solutions: There are two solutions in my view. One is to update 30 or so users, wait some time, and then dispatch after that another queue to get again some data. The second solution is to live with having all data in English, make a string that each user saves in the cloud. So for other users retrieving contacts, it's just a matter of fetching a string from the database rather than checking GPS data "on the fly".

Apple Swift - Unable to retrieve data from CMPedometer's handler

I am trying out Swift with some of the new iOS 8 API's and have tried the best part of the day to get the CMPedometer queryPedometerDataFromDate API to return any data within the handler. I believe its an error on my part, getting a little confused with the syntax.
Here is my code, with comments on what prints out:
var ped = CMPedometer()
var stepsTaken = NSNumber(int: 0)
println(dateNow) // 2014-06-07 21:23:55 +0000
println(dateMidnight) // 2014-06-07 00:00:00 +0000
ped.queryPedometerDataFromDate(dateMidnight, toDate: dateNow, withHandler:{
data, error in
println("Test1") // Does not print
println(error) // Does not print
stepsTaken = data.numberOfSteps
})
println("My Int Value \(stepsTaken)") // My Int Value 0
It works for me with CMPedometer queryPedometerDataFromDate, if i define the CMPedometer as a class wide constant:
let pedometer = CMPedometer()
and then in a func i'm using:
self.pedometer.queryPedometerDataFromDate(today, toDate: now, withHandler: ...
A code sample from Hipster.
import CoreMotion
let lengthFormatter = NSLengthFormatter()
let pedometer = CMPedometer()
pedometer.startPedometerUpdatesFromDate(NSDate(), withHandler: { data, error in
if !error {
println("Steps Taken: \(data.numberOfSteps)")
var distance = data.distance.doubleValue
println("Distance: \(lengthFormatter.stringFromMeters(distance))")
var time = data.endDate.timeIntervalSinceDate(data.startDate)
var speed = distance / time
println("Speed: \(lengthFormatter.stringFromMeters(speed)) / s")
}
})
After many hours playing around with the syntax, thinking I have made a rookie error, I tried alternative API's and found that the following works to grab pedometer data:
var ped2 = CMStepCounter()
ped2.queryStepCountStartingFrom(dateMidnight, to: dateNow, toQueue: NSOperationQueue(), withHandler:{data, error in
println("Test 2") // "Test 2"
println(data) // 491 (I really should go for a walk!)
println(error) // nil
})
I will file a radar with Apple as it looks like a bug in the new API. Also the CMStepCounter class is due to be deprecated for CMPedometer. From CMStepCounter.h:
NS_CLASS_DEPRECATED_IOS(7_0,8_0,"Use CMPedometer instead")
You're querying the data, which goes onto another thread. The execution continues after your call, and your NSNumber is still at 0. By the time you've come back you've printed the message a long time ago, and have no way of recovering the stepsTaken.
You need to keep a reference to your ped variable. Something like:
self.ped = CMPedometer();
Currently your ped variable is going out of scope before the handler is even called.

Resources