HKAnchoredQuery update handler not working properly - ios

I wrote a code to get heart rate values from Health kit. The code is working fine but when the new heart values are updated in Health kit. I have to come to main screen and then multitask my app to get the updated results. What my aim is to get the updated result on my app without reopening or multitasking it, please help as I am new to iOS development.
My code:-
-(void)get_heartRates
{
//code to get the updated heart beats
HKSampleType *sampleType =
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKObserverQuery *query =
[[HKObserverQuery alloc]
initWithSampleType:sampleType
predicate:nil
updateHandler:^(HKObserverQuery *query,
HKObserverQueryCompletionHandler completionHandler,
NSError *error) {
if (error) {
NSLog(#"error occured while setting observer. %# ",
error.localizedDescription);
abort();
}
// Take whatever steps are necessary to update your app's data and UI
// This may involve executing other queries
[self executeAnchoredQuery];
// If you have subscribed for background updates you must call the completion handler here.
// completionHandler();
}];
[self.healthStore executeQuery:query];
}
-(void)executeAnchoredQuery
{
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);
NSLog(#"%f", avg_heartBeats);
}

See if the following code could help you...
HKSampleType *sampleType =
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKObserverQuery *query =
[[HKObserverQuery alloc]
initWithSampleType:sampleType
predicate:nil
updateHandler:^(HKObserverQuery *query,
HKObserverQueryCompletionHandler completionHandler,
NSError *error) {
if (error) {
NSLog(#"error occured while setting observer. %# ",
error.localizedDescription);
abort();
}
// Take whatever steps are necessary to update your app's data and UI
// This may involve executing other queries
[self executeAnchoredQuery];
// If you have subscribed for background updates you must call the completion handler here.
// completionHandler();
}];
[self.healthStore executeQuery:query];
and then the function where you write you anchoredQuery code, this may give you an idea of the code flow :
-(void)executeAnchoredQuery
{
HKSampleType *sampleType =
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKAnchoredObjectQuery *query =
[[HKAnchoredObjectQuery alloc]
initWithType:sampleType
predicate:nil
anchor:self.anchor
limit:HKObjectQueryNoLimit
resultsHandler:^(HKAnchoredObjectQuery * _Nonnull query,
NSArray<__kindof HKSample *> * _Nullable sampleObjects,
NSArray<HKDeletedObject *> * _Nullable deletedObjects,
HKQueryAnchor * _Nullable newAnchor,
NSError * _Nullable error) {
if (error != nil) {
// Add proper error handling here...
NSLog(#"*** Unable to query for step counts: %# ***",
error.localizedDescription);
abort();
}
// Process the results...
self.anchor = newAnchor;
for (HKQuantitySample *sample in sampleObjects) {
[self addStepCountSample:sample];
}
for (HKDeletedObject *sample in deletedObjects) {
[self removeStepCountSample:sample];
}
NSLog(#"Done!");
}];
[self.healthStore executeQuery:query];
}
Please go through apple developer docs for further details.

Related

Unable to share a CKRecord

I wrote the code below to share a CKRecord:
CKRecordZone *restaurantsZone = [[CKRecordZone alloc] initWithZoneName:#"RestaurantsZone"];
CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:self.recordName zoneID:restaurantsZone.zoneID];
CKRecord *record = [[CKRecord alloc] initWithRecordType:#"Restaurant" recordID:recordID];
[record setValue:self.restaurant forKey:#"name"];
UICloudSharingController *cloudSharingController = [[UICloudSharingController alloc] initWithPreparationHandler:^(UICloudSharingController * _Nonnull controller, void (^ _Nonnull preparationCompletionHandler)(CKShare * _Nullable, CKContainer * _Nullable, NSError * _Nullable)) {
[self shareRootRecord:record name:self.restaurant completion:preparationCompletionHandler];
}];
cloudSharingController.delegate = self;
[self presentViewController:cloudSharingController animated:YES completion:nil];
And the shareRootRecord function:
- (void)shareRootRecord:(CKRecord *)rootRecord name:(NSString *)name completion:(void (^)(CKShare * _Nullable share, CKContainer * _Nullable container, NSError * _Nullable error))completion
{
CKShare *shareRecord = [[CKShare alloc] initWithRootRecord:rootRecord];
shareRecord[CKShareTitleKey] = name;
NSArray *recordsToSave = #[rootRecord, shareRecord];
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *privateDatabase = [container sharedCloudDatabase];
CKModifyRecordsOperation *operation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave recordIDsToDelete:#[]];
[operation setPerRecordCompletionBlock:^(CKRecord * _Nullable record, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", [error localizedDescription]);
}
}];
[operation setModifyRecordsCompletionBlock:^(NSArray<CKRecord *> * _Nullable savedRecords, NSArray<CKRecordID *> * _Nullable deletedRecordIDs, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", [error localizedDescription]);
}
completion(shareRecord, container, error);
}];
[privateDatabase addOperation:operation];
}
Now, when I run this code, the following error is thrown: Only shared zones can be accessed in the shared DB. I can't seem to be able to figure out why, though. Any ideas?
Make sure the CKRecord you want to share is already in the owners privateDB before you share it.
When a participant accepts the share, that's when the record will appear in the participants sharedDB.
This code creates a record and a share and then tries to modify the sharedDB of the owner with the records.
Conceptually, you want to share a a record in an owners privateDB with a participant. The sharedDB of a participant acts as a window into the privateDB of the owner.

I have fetched the contact using cncontacts and able to display them on console screen, but not on table view

How to display contact in table view?
I have fetched the contacts via CNContactStore iOS 9.
- (void) getContacts {
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted == YES) {
//keys with fetching properties
NSArray *keys = #[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey,CNContactImageDataKey];
NSString *containerId = store.defaultContainerIdentifier;
NSPredicate *predicate = [CNContact predicateForContactsInContainerWithIdentifier:containerId];
NSError *error;
NSArray *cnContactsarray = [store unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:&error];
if (error) {
NSLog(#"error fetching contacts %#", error);
}
else {
for (CNContact *contact in cnContactsarray) {
//store all the contacts as per your requirement
NSLog(#"Name : %#",contact.givenName);
NSLog(#"Id : %#",contact.identifier);//the contact id which you want
[_ContactsCN addObject:contact];
}
}
NSLog(#"array %#",_ContactsCN);
}
}];
}
Any help will be appreciated.
You need to reload data on main thread when your fetch contact from CNContact is complete
to run on main thread
dispatch_async(dispatch_get_main_queue(), ^{// reload your table here
});

Getting Private User Record in CloudKit

I am attempting to get a Users' saved data in CloudKit. I can see the record in the CloudKit Dashboard, but am unable to get to it via code in app.
-(void)getUserRecordID {
CKContainer *defaultContainer =[CKContainer defaultContainer];
[defaultContainer fetchUserRecordIDWithCompletionHandler:^(CKRecordID *recordID, NSError *error) {
if (!error) {
[defaultContainer fetchUserRecordIDWithCompletionHandler:^(CKRecordID *recordID, NSError *error) {
self.userRecordID = recordID;
NSLog(#"user record id: %#",recordID);
[self getUserRecord];
}];
}
else {
NSLog(#"error: %#",error.localizedDescription);
}
}];
}
-(void)getUserRecord {
CKContainer *defaultContainer =[CKContainer defaultContainer];
CKDatabase *publicDatabase = defaultContainer.publicCloudDatabase;
[publicDatabase fetchRecordWithID:self.userRecordID completionHandler:^(CKRecord *userRecord, NSError *error) {
if (!error) {
NSLog(#"current coins: %#",userRecord);
}
else {
NSLog(#"error: %#",error.localizedDescription);
}
}];
}
This gets me the User record information, but not the ID's of the saved private records. How can I get them?
Figured out the solution. You have to query for private records using CKQuery and NSPredicate.
-(void)getUserRecordID {
CKContainer *defaultContainer =[CKContainer defaultContainer];
[defaultContainer fetchUserRecordIDWithCompletionHandler:^(CKRecordID *recordID, NSError *error) {
if (!error) {
self.userRecordID = recordID;
NSLog(#"user record id: %#",recordID.recordName);
[self getStatsRecord];
}
else {
NSLog(#"error: %#",error.localizedDescription);
}
}];
}
-(void)getStatsRecord {
CKDatabase *privateDatabase = [[CKContainer defaultContainer] privateCloudDatabase];
CKReference *reference = [[CKReference alloc] initWithRecordID:self.userRecordID action:CKReferenceActionNone];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"creatorUserRecordID = %#", reference];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Stats" predicate:predicate];
[privateDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
if (error) {
NSLog(#"error: %#",error.localizedDescription);
}
else {
if (![results firstObject]) {
[self createBlankRecord];
}
else {
CKRecord *record = [results firstObject];
NSLog(#"%#",record);
}
}
}];
}

Does HKObserverQuery can receive notification when the application not running(Killed)?

My requirement is to register for any one of the Health data like steps, weight, heart rate, etc., for background delivery using enableBackgroundDeliveryForType: method. And then create a Observer query for the same Health data to check.
In this scenario, if my application is killed by the user forcely and did changes to Health data using Health app. By this stage is it possible to get the notification to My application?
Is it mandatory to register for any of the background modes, to get notified for health data modifications through HKObserverQuery?
EDIT 1:
- (void)requestAuthorizationToShareTypes:(NSSet *)typesToShare readTypes:(NSSet *)typesToRead completion:(void (^)(BOOL, NSError *))completion
{
if ([HKHealthStore isHealthDataAvailable]) {
[self.healthStore requestAuthorizationToShareTypes:typesToShare readTypes:typesToRead completion:^(BOOL success, NSError *error) {
if(success)
{
self.authorizationSuccess = YES;
completion(YES, nil);
}
else
{
[MyUtilities showAlertWithTitle:#"Authorization fail!" message:#"You didn't allow to access Health data."];
completion(NO, error);
return;
}
}];
}
else
{
[MyUtilities showAlertWithTitle:#"Sorry!" message:#"Your device does not support Health data."];
NSDictionary *errorDictionary = #{ NSLocalizedDescriptionKey : #"Your device doesn't support Health Kit."};
NSError *error = [[NSError alloc] initWithDomain:#"" code:2 userInfo:errorDictionary];
completion(NO, error);
return;
}
}
- (void)authorizeConsumeHealth:(void (^)(BOOL, NSError *))completion
{
NSSet *readObjectTypes = [NSSet setWithObjects:
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDietaryCaffeine],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDietaryCalcium],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDietaryChloride],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDietaryIron],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic],
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureDiastolic], [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount],
nil];
[self requestAuthorizationToShareTypes:nil readTypes:readObjectTypes completion:^(BOOL success, NSError *error) {
if(completion)
{
completion(success, error);
[self observeStepCountChanges];
}
}];
}
- (void)observeStepCountChanges
{
HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
[self.healthStore enableBackgroundDeliveryForType:sampleType frequency:HKUpdateFrequencyImmediate withCompletion:^(BOOL success, NSError *error) {}];
HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:sampleType predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
if(error)
{
NSLog(#"Steps count observer completion block. Error: %#", error);
}
else
{
NSLog(#"Steps count observer completion block");
}
}];
[self.healthStore executeQuery:query];
}
Please help me. Thanks.

Calling specific method after another method

I'm retrieving data from my parse database and looping it into an NSMutableArray. This is in my getHomes method, which is being called in viewDidLoad. The NSMutableArray contain various UIImages, which CGSize' i'm looping through. This method is called getCellHeights. I want to call this method right after the getHomes method, but stacking them like this in viewdidLoad does not do the trick. The getCellHeights method is not running since there is no objects in the NSMutableArray, cause its not completed with the retrieving of data.
How can i make sure that the getCellHeights is not running before the getHomes method is completed?
getCellHeights
-(void)getcellHeights {
for (int i = 0; i < homesDic.count; i++) {
UIImage *image = [[homesDic objectAtIndex:i] objectForKey:#"image"];
CGFloat divider = image.size.width/145;
CGFloat wantedWidth = 145;
CGFloat wantedHeight = image.size.height/divider;
NSLog(#"width: %f height: %f", wantedWidth, wantedHeight);
CGSize size = CGSizeMake(wantedWidth, wantedHeight);
[_cellSizes addObject:[NSValue valueWithCGSize:size]];
}
}
getHomes
-(void)getHomes {
PFQuery *query = [PFQuery queryWithClassName:#"Homes"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects1, NSError *error) {
if (!error) {
for (PFObject *object in objects1) {
PFQuery *userQ = [PFUser query];
[userQ getObjectInBackgroundWithId:[object objectForKey:#"userId"] block:^(PFObject *userName, NSError *error) {
NSLog(#"%#", userName);
[[userName objectForKey:#"file"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
UIImage *profileImage = [UIImage imageWithData:data];
PFQuery *homeQ = [PFQuery queryWithClassName:#"homeImages"];
[homeQ whereKey:#"homeId" equalTo:object.objectId];
[homeQ whereKey:#"number" equalTo:#(0)];
[homeQ findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
PFQuery *cityQ = [PFQuery queryWithClassName:#"City"];
id zip = [NSNumber numberWithInteger: [[object objectForKey:#"zip"] intValue]];
[cityQ whereKey:#"fra" greaterThanOrEqualTo:zip];
[cityQ whereKey:#"fra" lessThanOrEqualTo:zip];
[cityQ findObjectsInBackgroundWithBlock:^(NSArray *cObject, NSError *error) {
if (!error) {
PFObject *cityObject = [cObject lastObject];
PFObject *image = [objects lastObject];
[[image objectForKey:#"Image"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:data];
NSMutableDictionary *flagDict = [[NSMutableDictionary alloc] init];
[flagDict setObject:[object objectForKey:#"title"] forKey:#"title"];
[flagDict setObject:image forKey:#"image"];
[flagDict setObject:[cityObject objectForKey:#"navn"] forKey:#"city"];
[flagDict setObject:[userName objectForKey:#"name"] forKey:#"name"];
[flagDict setObject:profileImage forKey:#"profileImage"];
[homesDic addObject:flagDict];
[self.collectionView reloadData];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
}];
}
}];
}];
}];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
The Blocks that you're passing to the Parse framework query methods are completion/callback Blocks; when the fetch has completed, they are run. Do whatever you need to do after the fetch -- including calling cellHeights -- in the appropriate completion Block.

Resources