Our fitness app was using three anchored object queries to stream heart rate, active calories and distance; it was working just fine until a few weeks ago when we started optimizing the rest of the app's performance to be more responsive and query samples for past workouts. We believe we've restored the queries back to the way they were before, and we have very similar code but written in Swift for a separate fitness app working fine, but these HKAnchoredObjectQuerys just don't return further results after the initial results are handled (yes, a workout session is active). We're a bit stumped. Can anyone take a look and suggest where we might have gone wrong?
Thank you!
Here's an example of the heart rate query:
- (HKQuery*) createHeartRateStreamingQuery: (NSDate *) workoutStartDate {
NSPredicate * predicate = [HKQuery predicateForSamplesWithStartDate:workoutStartDate endDate:nil options:HKQueryOptionNone];
HKQueryAnchor *anchor = HKAnchoredObjectQueryNoAnchor;
if (_HRAnchor) {
anchor = _HRAnchor;
}
HKQuantityType * quantityType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKAnchoredObjectQuery *query =
[[HKAnchoredObjectQuery alloc]
initWithType:quantityType
predicate:predicate
anchor:anchor
limit:HKObjectQueryNoLimit
resultsHandler:^(HKAnchoredObjectQuery * query,
NSArray<HKSample *> * sampleObjects,
NSArray<HKDeletedObject *> * deletedObjects,
HKQueryAnchor * newAnchor,
NSError * error) {
if (error) {
NSLog(#"*** %s An error occured while performing the heartrate anchored object query. %# ***",
__PRETTY_FUNCTION__, error.localizedDescription);
abort();
} else {
if (newAnchor) {
_HRAnchor = newAnchor;
NSLog(#"*** %s: %i samples returned for startDate %# ***", __PRETTY_FUNCTION__, sampleObjects.count, workoutStartDate);
for (HKQuantitySample *sample in sampleObjects) {
[self updateHeartRate:sample];
}
for (HKDeletedObject *sample in deletedObjects) {
// [self removeHRSamples: sample]; //not using this for now
}
}
}
}];
return query;
}
- (void) updateHeartRate: (HKQuantitySample *) HRSample {
NSLog(#"%s: %#", __PRETTY_FUNCTION__, HRSample);
[_workoutHRSamplesArray addObject: HRSample];
}
In order to stream samples using an HKAnchoredObjectQuery, you must set the updateHandler property on the query. The results handler you provided to the initializer is only intended to run once for the existing samples that match your query.
Related
I have an entity named Geometry which is related to a Plot entity. In our piece of code, given the Plot and some downloaded data stored in a NSDictionary, we must get the Geometry and set some NSString properties but, after doing that, I find that the relationship between entities is lost.
NSError * saveError = nil;
NSFetchRequest * request = [[NSFetchRequest alloc] initWithEntityName:[Geometry entityName]];
request.predicate = [NSPredicate predicateWithFormat:#"plot == %#", plot];
NSError * error = nil;
NSArray * results = [context executeFetchRequest:request error:&error];
Geometry * __block geometry = nil;
if ([results count] > 0) {
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
Geometry * geometryObject = obj;
if ([geometryObject.gid isEqualToString:[NSString stringWithFormat:#"%#", [data valueForKey:#"gid"]]]) {
geometry = geometryObject;
stop = YES;
}
}];
}
if (geometry != nil) {
[geometry setPolygon:[NSString stringWithFormat:#"%#", [data valueForKey:#"polygon"]]];
}
if (![context save:&saveError]) {
NSLog(#"%#", saveError);
}
The first time I run this code results have one object, but the next time I run this there is no results.
Assume everything outside the scope of this piece of code is working right. Any hint or clue about why this happen? How can I solve this?
EDIT: The problem has been solved outside the scope of the code posted and outside the scope of this question. I should have properly reviewed the code further.
There is nothing in your code that breaks the relationship. The error must be elsewhere.
You have a Plot object, so you can get the geometries with plot.geometries without a fetch request, and filter them without a loop:
Geometry *geometry = [plot.geometries
filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:#"gid = %#", gidString]]
.firstObject
where geometries is the name of the inverse relationship for plot.
You can now set the polygon property and save. Check your setPolygon method if you are not removing the relationship.
I'm working with HealthKit to read steps data from my iOS device.
here is my code:
if ([HKHealthStore isHealthDataAvailable]) {
__block double stepsCount = 0.0;
self.healthStore = [[HKHealthStore alloc] init];
NSSet *stepsType =[NSSet setWithObject:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]];
[self.healthStore requestAuthorizationToShareTypes:nil readTypes:stepsType completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:nil limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if (error != nil) {
NSLog(#"results: %lu", (unsigned long)[results count]);
for (HKQuantitySample *result in results) {
stepsCount += [result.quantity doubleValueForUnit:[HKUnit countUnit]];
}
NSLog(#"Steps Count: %f", stepsCount);
} else {
NSLog(#"error:%#", error);
}];
[self.healthStore executeQuery:sampleQuery];
[self.healthStore stopQuery:sampleQuery];
NSLog(#"steps:%f",stepsCount);
}
}];
}
I build and run the code on an iPhone6 which does have steps data and in the Settings -> Privacy -> Health, the app does have been allowed to read data, but the log area only shows:
steps:0.000000
I put a break point on the for-loop and on the NSLog(#"error:%#", error), but the app does not break.
Anybody can help?
Try this code u just change the start date and End date.
-(void) getQuantityResult
{
NSInteger limit = 0;
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:currentDate endDate:[[NSDate date]dateByAddingTimeInterval:60*60*24*3] options:HKQueryOptionStrictStartDate];
NSString *endKey = HKSampleSortIdentifierEndDate;
NSSortDescriptor *endDate = [NSSortDescriptor sortDescriptorWithKey: endKey ascending: NO];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType[HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]
predicate: predicate
limit: limit
sortDescriptors: #[endDate]
resultsHandler:^(HKSampleQuery *query, NSArray* results, NSError *error){
dispatch_async(dispatch_get_main_queue(), ^{
// sends the data using HTTP
int dailyAVG = 0;
for(HKQuantitySample *samples in results)
{
dailyAVG += [[samples quantity] doubleValueForUnit:[HKUnit countUnit]];
}
lblPrint.text = [NSString stringWithFormat:#"%d",dailyAVG];
NSLog(#"%#",lblPrint.text);
NSLog(#"%#",#"Done");
});
}];
[self.healthStore executeQuery:query];
}
Your code stops the query immediately, before it has a chance to run. For this query, there is no reason to call stopQuery: at all unless you want to cancel a query before it finishes. Since the query is not long lived (it doesn't have an updateHandler), it will stop immediately after the resultsHandler is called.
The second problem is that your code attempts to log step count too soon. The query runs asynchronously, and the resultsHandler will be called on a background thread once the query completes. I'd suggest logging stepsCount inside the block.
Finally, if you want to count the user's steps you should us an HKStatisticsQuery instead of summing the results of an HKSampleQuery. HKStatisticsQuery is more efficient and will yield correct results when there are multiple sources of overlapping data in HealthKit. Your current implementation will double count the user's steps if they have both an iPhone and an Apple Watch, for instance.
I wrote this code to get heart rate I am using NSArray and getting average of heart rate.
Now the question is the Apple Watch is updating data in Health Kit and I want the updated average heart beats being refreshed at every 1 minutes.
I am stuck at this point, please help?
-(double)get_heartRates
{
//code to get the updated heart beats
NSDate *startDate1 = [NSDate distantPast];
NSPredicate *Predicate = [HKQuery predicateForSamplesWithStartDate:startDate1 endDate:[NSDate date] options:HKQueryOptionStrictEndDate];
HKSampleType *object = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
sum_Of_HeartRates=0.0;
HKAnchoredObjectQuery *heartQuery = [[HKAnchoredObjectQuery alloc] initWithType:object predicate:Predicate anchor:self.lastAnchor limit:0 resultsHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
NSLog(#"Sample counts:%ld",sampleObjects.count);
for(int i=0;i<(int)sampleObjects.count;i++)
{
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects objectAtIndex:i];
HKQuantity *quantity = sample.quantity;
double bpm_Values= [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]];
sum_Of_HeartRates=sum_Of_HeartRates+bpm_Values;
}
avg_heartBeats=sum_Of_HeartRates/(int)sampleObjects.count;
}];
[heartQuery setUpdateHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *SampleArray, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *Anchor, NSError *error) {
HKQuantitySample *sample = (HKQuantitySample *)[SampleArray objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
new_Updated_Data =[quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]];
NSLog(#"new quantity:%f",new_Updated_Data);
}];
[self.healthStore executeQuery:heartQuery];
NSLog(#"updated data %f",new_Updated_Data);
return avg_heartBeats;
//todo:- to get background update fast and easy
}
Use HKObserverQuery to get a long running query that is updated in background:
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKObserverQuery_Class/index.html
As long as no HKWorkoutSessionis running, your watch measures the heart rate every 10 minutes, so you don't get more values. When a HKWorkoutSession is running on your watch, you get values more frequently.
If you want to dive into HKWorkoutSession, Allan has a detailed tutorial: https://developer.apple.com/videos/play/wwdc2015/203/
I'm using MagicalRecord to change my custom CoreData functions with something better.
I have two entities: Offer (videogame offers) and System (consoles, PC, etx). An Offer can have one or many Systems, and I get all the data from a Parse query, where I save all my data.
I only have 8 Systems so when my app starts, I fetch all of them and save with Magical Record. Then I call my method to fetch some offers and transform the PFObjects from Parse into entities.
This is how
+ (void)createOrUpdateOffersWithArray:(NSArray *)objects completion:(void (^)())completion
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
for (PFObject *object in objects) {
Offer *offer = [Offer MR_findFirstByAttribute:#"offerId" withValue:object.objectId inContext:localContext];
if (offer == nil) {
offer = [Offer MR_createEntityInContext:localContext];
}
// Here I set all the offer values (title, date, etc)...
NSSet *offerSavedSystems = [offer mutableSetValueForKey:#"systems"];
if (offerSavedSystems == nil) {
offerSavedSystems = [[NSMutableSet alloc] init];
}
/* This is a set of all the systems related with the offer */
NSArray *offerSystems = [object objectForKey:#"sistemas"];
NSMutableSet *updatedSystems = [[NSMutableSet alloc] init];
/* Here I query one of my System entities for each in the "offerSystems" array */
for (NSString *systemKey in offerSystems) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
[updatedSystems addObject:system];
}
offer.systems = updatedSystems;
}
} completion:^(BOOL contextDidSave, NSError *error) {
/* UI update*/
}];
}
The weird this happens inside the last for loop. Despite I'm sure that all the 8 systems are inside my CoreData model, this line
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
returns nil
But the most weird thing is that using NSLOG just before the for loop like this
NSLog(#"SYSTEMS %d", [System MR_countOfEntities]);
NSLog(#"SYSTEMS WITH LOCAL CONTEXT %d", [System MR_countOfEntitiesWithContext:localContext]);
I got this
SYSTEMS 8
SYSTEMS WITH LOCAL CONTEXT 0
My Systems are previously saved with this method
+ (void)initializeSystems
{
NSArray *systemsKeys = [[self systems] allKeys];
for (NSString *systemKey in systemsKeys) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey];
if (system == nil) {
system = [System MR_createEntity];
}
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
system.key = systemKey;
system.name = [self literalForSystem:systemKey];
system.subscribed = [NSNumber numberWithBool:NO];
}];
}
}
What I'm doing wrong?
Thanks for your time
There are several possible problems with this line of code
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
systemKey value does not exist inside all of your entities.
system.key value not setted (or nil)
So, check firstly - fetch all system entities and log 'key' value. Insure that your key really exist.
Secondly,it's better to refactor your code for background saving
+ (void)initializeSystemsWithCompletion:(void (^)())completion
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *systemsKeys = [[self systems] allKeys];
for (NSString *systemKey in systemsKeys) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
if (system == nil) {
system = [System MR_createEntityInContext:localContext];
}
system.key = systemKey;
system.name = [self literalForSystem:systemKey];
system.subscribed = [NSNumber numberWithBool:NO];
}
} completion:^(BOOL success, NSError *error) {
}];
}
Hi I need to download over 2000 records from azure , the maximum you can download is 1000 at the time , so I need to use a completion handler to download 200 at the time.
They posted this code as an example but I don't know how to use.
If I copy this to Xcode there is an error
(bool)loadResults() - Error " Expect Method Body "
Returning data in pages
Mobile Services limits the amount of records that are returned in a single response. To control the number of records displayed to your users you must implement a paging system. Paging is performed by using the following three properties of the MSQuery object:
BOOL includeTotalCount
NSInteger fetchLimit
NSInteger fetchOffset
In the following example, a simple function requests 20 records from the server and then appends them to the local collection of previously loaded records:
- (bool) loadResults() {
MSQuery *query = [self.table query];
query.includeTotalCount = YES;
query.fetchLimit = 20;
query.fetchOffset = self.loadedItems.count;
[query readWithCompletion:(NSArray *items, NSInteger totalCount, NSError *error) {
if(!error) {
//add the items to our local copy
[self.loadedItems addObjectsFromArray:items];
//set a flag to keep track if there are any additional records we need to load
self.moreResults = (self.loadedItems.count < totalCount);
}
}];
}
thanks for your help.
If you are getting Error " Expect Method Body " then you copied it into your code incorrectly and there is a formatting issue.
If you want to load data with paging in a single call, I would do something like this:
in your .h file declare
typedef void (^CompletionBlock) ();
#property (nonatomic, strong) NSMutableArray *results;
in your .m file
- (void)loadData
{
self.results = [[NSMutableArray alloc] init];
MSClient *client = [MSClient clientWithApplicationURLString:#"YOUR_URL" applicationKey:#"YOUR_KEY"]
MSTable *table = [client tableWithName:#"YOUR_TABLE"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"YOUR_SELECT_FILTER"];
MSQuery *query = [[MSQuery alloc] initWithTable:table predicate:predicate];
//note the predicate is optional. If you want all rows skip the predicate
[self loadDataRecursiveForQuery:query withCompletion:^{
//do whatever you need to do once the data load is complete
}];
}
- (void)loadDataRecursiveForQuery:(MSQuery *)query withCompletion:(CompletionBlock)completion
{
query.includeTotalCount = YES;
query.fetchLimit = 1000; //note: you can adjust this to whatever amount is optimum
query.fetchOffset = self.results.count;
[query readWithCompletion:(NSArray *items, NSInteger totalCount, NSError *error) {
if(!error) {
//add the items to our local copy
[self.results addObjectsFromArray:items];
if (totalCount > [results count]) {
[self loadDataRecursiveForQuery:query withCompletion:completion];
} else {
completion();
}
}
}];
}
Note: I haven't tested this code, but it should work more or less.