I am writing a simple app to monitor the heart rate (HKQuantityTypeIdentifierHeartRate) from HealthKit whenever a new health rate value is written to HealthKit.
As seen at WWDC2015 (session 203) I am using a HKAnchoredObjectQuery which should work for adding and deleting objects. Whenever I start the app I am calling the HKQuery for the newest objects and executingQuery which works fine!!! But I am getting no new samples even if the samples are there, but if I bring the app to the background and again to the foreground I am getting all the new heart rates. IS IT A BUG? Or what shall I do to monitor the heart rate without bringing the app to the back- and foreground?
Here is the code I am using (everything is stored in the AppDelegate), I am calling [self requestAccessDataTypes];
from didFinishLaunchingWithOptions:
[healthStore enableBackgroundDeliveryForType:sampleType frequency:HKUpdateFrequencyImmediate withCompletion:^(BOOL success, NSError *error) {}];
HKQuery *query = [self createHeartRateStreamingQuery:datum];
if (query) {
[healthStore executeQuery:query];
}
else
{
NSLog(@"workout can not start");
}
-(HKQuery*)createHeartRateStreamingQuery:(NSDate*)workoutStartDate
{
NSLog(@"%@ - createHeartRateStreamingQuery", [self class]);
if ([HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate]) {
HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKAnchoredObjectQuery * heartRateQuery = [[HKAnchoredObjectQuery alloc] initWithType:quantityType predicate:nil anchor:anchor limit:HKObjectQueryNoLimit resultsHandler:^(HKAnchoredObjectQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable sampleObjects, NSArray<HKDeletedObject *> * _Nullable deletedObjects, HKQueryAnchor * _Nullable newAnchor, NSError * _Nullable error) {
if (!error) {
anchor = newAnchor;
[self updateHeartRate:sampleObjects];
}
}];
heartRateQuery.updateHandler = ^void(HKAnchoredObjectQuery *query, NSArray<__kindof HKSample *> * __nullable addedObjects, NSArray<HKDeletedObject *> * __nullable deletedObjects, HKQueryAnchor * __nullable newAnchor, NSError * __nullable error)
{
if (!error) {
anchor = newAnchor;
[self updateHeartRate:addedObjects];
}
};
return heartRateQuery;
}
return nil;
}
Right now (iOS 9.1, WatchOS 2.0.1), it is not possible to get the latest data from HealthKit through an iOS app. It was possible in the WWDC demo because the code was running on the WatchOS app's ExtensionDelegate rather than on the iOS app. There is a rdar bug report filed here.
To get the latest data on iOS, it's not possible without creating a WatchOS app. With a WatchOS app you could use a Workout Session and Watch Connectivity to send heart rate data to the iOS app every time it changes.
Of course, this doesn't help if your heart rate data isn't coming from an Apple Watch. Hopefully it'll be fixed in an upcoming release.
You are missing a crucial piece for observing changes in HealthKit. It's called
HKObserverQuery
.Docs
Recap:
You have to wrap your
HKAnchoredObjectQuery
inHKObserverQuery
with background delivery enabled in order to get notified about updates. You can then execute your query whenever that happens.Note 1:
HKObserverQuery
's update handler will NOT give you any Apple Health data samples. You still have to execute yourHKAnchoredObjectQuery
with a proper anchor to get the samples.Note 2: You have to set up
HKObserverQuery
every time your app launches.For further info, look at my answer here.