QueryOp with recordID - ios

I had a recordID.recordName (singleID in this example) and that I wanted to query with queryOp; after some searching and experimentation I managed to put this code together... which works, but is it really correct, is there a shorter path perhaps.
CKRecordID *wellKnownIDx = [[CKRecordID alloc] initWithRecordName:singleID];
CKReference *singleREX = [[CKReference alloc] initWithRecordID:wellKnownIDx action:CKReferenceActionDeleteSelf];
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.blah"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"recordID == %#",singleREX];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Courses" predicate:predicatex];
CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.resultsLimit = 1;
queryOp.recordFetchedBlock = ^(CKRecord *results)
{
NSLog(#"Student Found %#",results.recordID.recordName);
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
NSLog(#"CKQueryCursor error %#", error);
};
[publicDatabase addOperation:queryOp];

What you have should work, but it looks like all you want to do is to fetch a record with a known recordID. There is no need to use a query for that. It's more code than necessary and will also require a query index on the field.
Instead, use the fetchRecordWithID method on CKDatabase:
https://developer.apple.com/library/ios/documentation/CloudKit/Reference/CKDatabase_class/index.html#//apple_ref/occ/instm/CKDatabase/fetchRecordWithID:completionHandler:

Related

iOS Healthkit reading workouts

Hi any one know how to fetch workouts data from HealthKit. i have seen in this tutorial http://www.raywenderlich.com/89733/healthkit-tutorial-with-swift-workouts it is in swift. i have tried in objective c based on that tutorial but getting results zero. there are questions for saving workouts but i want to read workouts data and display.
HKWorkoutType *workouttype = [HKWorkoutType workoutType];
HKWorkout *workout;
NSDate *startDate, *endDate;
NSDate *date1 = [NSDate date];
int daysTominus = -2;
startDate = [date1 dateByAddingTimeInterval:60*60*24*daysTominus];
int daysToAdd = 1;
NSDate *newDate1 = [date1 dateByAddingTimeInterval:60*60*24*daysToAdd];
endDate = newDate1;
workout = [HKWorkout workoutWithActivityType:HKWorkoutActivityTypeSwimming startDate:startDate endDate:endDate];
NSPredicate *predicate = [HKQuery predicateForObjectsFromWorkout:workout];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:YES];
HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:workouttype
predicate:predicate
limit:HKObjectQueryNoLimit
sortDescriptors:#[sortDescriptor]
resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error)
{
if(!error && results){
for(HKQuantitySample *samples in results)
{
// your code here
NSLog(#"%#",samples);
}
}
}];
// Execute the query
[healthStore executeQuery:sampleQuery];
The problem seems to be your predicate.
This code works for me, I used running because I don't have swimming data but you can change it back to swimming:
-(void)retrieveWorkouts{
// 1. Predicate to read only running workouts
NSPredicate *predicate = [HKQuery predicateForWorkoutsWithWorkoutActivityType:HKWorkoutActivityTypeWalking];
// 2. Order the workouts by date
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:HKSampleSortIdentifierStartDate ascending:false];
// 3. Create the query
HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:[HKWorkoutType workoutType]
predicate:predicate
limit:HKObjectQueryNoLimit
sortDescriptors:#[sortDescriptor]
resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error)
{
if(!error && results){
NSLog(#"Retrieved the following workouts");
for(HKQuantitySample *samples in results)
{
// your code here
HKWorkout *workout = (HKWorkout *)samples;
NSLog(#"%f",workout);
}
}else{
NSLog(#"Error retrieving workouts %#",error);
}
}];
// Execute the query
[healthStore executeQuery:sampleQuery];
}

CKFetchRecordsOperation + CKQueryOperations ... what am I missing?

Managed to cobble together a CKFetchRecordsOperation after much searching for sample code; and here it is... but I must have missed something. Don't get me wrong it works a treat... but...
To execute a CKFetchRecordsOperation you need an NSArray of CKRecordIDs; to get a NSArray of CKRecordIDs, you need to execute CKQuery thru which you can build your NSArray of CKRecordIDs.
But wait a minute, the process of extracting the CKRecordIDs uses a CKQuery, thru which I could simply download the CKRecords anyway?
How do you get your NSArray of CKRecordIDs if not with a CKQuery?
-(void)doSingleBeaconsDownload
{
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"iBeaconConfig = %#",iBeaconsConfirmed.giReferenceID];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Running" predicate:predicatex];
[self.delegate performSelectorOnMainThread:#selector(processCompleted:) withObject:#"Downloading Configuration ..." waitUntilDone:YES];
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
if (error) {
NSLog(#"Batch Download Error iCloud error %#",error);
}
else {
NSMutableArray *rex2download = [[NSMutableArray alloc] init];
for (CKRecord *rex in results) {
[rex2download addObject:rex.recordID];
}
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:rex2download];
/* fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
if (error) {
// Retain the record IDs for failed fetches
}
else {
// Do something with each record downloaded
}
};*/
fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded) {
if (error) {
// damn ...
} else {
NSLog(#"Downloaded %f", recordsDownloaded);
}
};
fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
if (error) {
// Failed to fetch all or some of the records
}
else {
for(CKRecord *record in results) {
NSLog(#"Fini download %lu",(unsigned long)[recordsByRecordID count]);
}
[self.delegate performSelectorOnMainThread:#selector(beaconsDownloaded:) withObject:noOf waitUntilDone:YES];
}
};
[publicDatabase addOperation:fetchRecordsOperation];
}
}];
}
From Apple Documentation: A CKFetchRecordsOperation object retrieves CKRecord objects (whose IDs you already know) from iCloud.
A CKQueryOperation is used to retrieve CKRecords from iCloud based on some Query, so you can get them even if you do not know their recordIDs. A CKFetchRecordsOperation is used ONLY when you have the CKRecordIDs. You can create a CKRecordID without accessing iCloud, and you can store them in any local storage you have.
A good use case, which I use for this kind of operation, is when you want to modify a CKRecord, you need to first retrieve it from iCloud (using CKFetchRecordsOperation) and then save it back using CKModifyRecordsOperation.
Have a look at the two WWDC 2014 Videos on CloudKit that explain this pretty well.
Thanks for your help! I managed to craft an CKQueryOperation into the code, but ... but my code is soon going be become unreadable with many more of these nested loops? Surely there is a more elegant way to link CKQuery/Fetch/Modify operations; tried dependancies but missing something still ?
-(void)doSingleBeaconsDownload
{
[self.delegate performSelectorOnMainThread:#selector(processCompleted:) withObject:#"Downloading Configuration ..." waitUntilDone:YES];
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"iBeaconConfig = %#",iBeaconsConfirmed.giReferenceID];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Running" predicate:predicatex];
CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.resultsLimit = CKQueryOperationMaximumResults;
NSMutableArray *rex2download = [[NSMutableArray alloc] init];
queryOp.recordFetchedBlock = ^(CKRecord *results)
{
[rex2download addObject:results.recordID];
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
// Cursor it seems contains a reference to a second call to it [required] if you try download more then 100 records
if (error) {
NSLog(#"Batch Download Error iCloud error %#",error);
}
else {
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:rex2download];
fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
if (error) {
// Retain the record IDs for failed fetches
}
else {
// Do something ..
}
};
fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded) {
if (error) {
// damn
} else {
NSLog(#"Downloaded X");
}
};
fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
if (error) {
// Failed to fetch all or some of the records
}
else {
… Final clean up
}
};
[publicDatabase addOperation:fetchRecordsOperation];
}
};
[publicDatabase addOperation:queryOp];
}
Thanks Harry; Here is my third and final working solution; used a singleton global variable to pass the data between the two CKQueryOperations; don't know if that is best/good practice, but it works
... seems a pity you cannot use something like this ...
[fetchRecordsOperation addDependency:queryOp]; &
[queue fetchRecordsOperation]; (doesn't compile)
Would be a far cleaner solution... anyway here is V3 for completeness..
-(void)doSingleBeaconsDownloadV3
{
NSLog(#"doQuery executing");
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"iBeaconConfig = %#",iBeaconsConfirmed.giReferenceID];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Running" predicate:predicatex];
CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.resultsLimit = CKQueryOperationMaximumResults;
//NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO];
//query.sortDescriptors = #[sortDescriptor];
queryOp.recordFetchedBlock = ^(CKRecord *results)
{
[iBeaconsConfirmed.giRex2Download addObject:results.recordID];
NSLog(#"fetched %lu",[iBeaconsConfirmed.giRex2Download count]);
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
if (error) {
NSLog(#"Batch Download Error iCloud error %#",error);
} else {
NSLog(#"fetched %lu",[iBeaconsConfirmed.giRex2Download count]);
[self DoFetchV2];
}
};
[publicDatabase addOperation:queryOp];
}
-(void)DoFetchV2
{
NSLog(#"dofetch executing %lu",[iBeaconsConfirmed.giRex2Download count]);
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:iBeaconsConfirmed.giRex2Download];
fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
if (error) {
// Retain the record IDs for failed fetches
}
else {
// Do something useful with data
}
};
fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded)
{
NSLog(#"Downloaded X");
};
fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
if (error) {
// Failed to fetch all or some of the records
} else {
NSLog(#"Fini download %lu",(unsigned long)[recordsByRecordID count]);
}
};
[publicDatabase addOperation:fetchRecordsOperation];
}

CloudKit : CKQuery showing error: error: The operation couldn’t be completed. (CKErrorDomain error 1.)

I working on iOS app implementing cloudkit but I need to query all the records with ID greater then a number. For example I have record with ID of 23:
here is my code:
CKContainer *myContainer = [CKContainer containerWithIdentifier:containerID];
CKDatabase *publicDatabase = [myContainer publicCloudDatabase];
CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:#"23"];
CKReference* recordToMatch = [[CKReference alloc] initWithRecordID:recordID action:CKReferenceActionNone];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"recordID >= %#", recordToMatch];
CKQuery *query = [[CKQuery alloc] initWithRecordType:recordType predicate:predicate];
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
if (error) {
NSLog(#"error: %#", error.localizedDescription);
}
else {
}
}];
But I'm getting the following error:
error: error: The operation couldn’t be completed. (CKErrorDomain error 1.)
Any of you knows how can I setup my NSPredicate in a way where I can get all the records with ID greater then 23 ?
I'll really appreciate your help.
For a query like this you could use a predicate like:
in Swift:
var predicate = NSPredicate(format: "recordID >= %#", CKRecordID(recordName: "23"))
In Objective C:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"recordID >= %#", [CKRecordID initWithRecordName:#"23"]];
Then I do assume that you created the CKRecords object originally while specifying this number. Otherwise recordID values will be assigned by CloudKit and will be a GUID.
Your recordID of 23 is stored as a CKRecordID type and not as a number thus you cannot use any numeric predicates like greater-than. You should create a new number field in your record for storing your integer IDs and query that instead.

Understanding results from HKSourceQuery, or Sources in general

I just did a HKSourceQuery and got some results. When I do a println of the results, I got this: <HKSource:0x156c1520 "Health" (com.apple.Health)>//description of the object
How do I use this to make a predicate using the HKQuery.predicateForObjectsFromSource(/* source goes here */)
Here is the sample code in Obj-c,
NSSortDescriptor *timeSortDesriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
HKSourceQuery *sourceQuery = [[HKSourceQuery alloc] initWithSampleType:quantityType samplePredicate:nil completionHandler:^(HKSourceQuery *query, NSSet *sources, NSError *error) {
//Here, sources is a set of all the HKSource objects available for "quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned"
HKSource *targetedSource = [[sources allObjects] firstObject];//Assume this as your targeted source
if(targetedSource)
{
NSPredicate *sourcePredicate = [HKQuery predicateForObjectsFromSource:targetedSource];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:quantityType predicate:sourcePredicate limit:HKObjectQueryNoLimit sortDescriptors:[NSArray arrayWithObject:timeSortDesriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
//results array contains the HKSampleSample objects, whose source is "targetedSource".
}];
[self.healthStore executeQuery:query];
}
}];
[self.healthStore executeQuery:sourceQuery];
UPDATE 1:
It is not possible to construct HKSource object manually using [HKSource alloc] init]. In HealthKit framework, Apple restricted creation of objects using init for most of the HK classes.
I believe that you can find your HKSource object from the sources set using the HKSource properties like name and bundleIdentifier.
Here is the sample code,
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.source.bundleIdentifier = 'com.XXXX.XXXXX'"];
NSArray *tempResults = [[sources allObjects] filteredArrayUsingPredicate:predicate];
HKSource *targetedSource = [tempResults firstObject];

use subquery in Cloudkit

Anyone knows how to use subquery in CloudKit? Here is my trying code:
// stringArray is String list in Cloudkit
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(stringArray, $fS, ANY $fS = %#).#count != 0", targetString];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"TestRecord" predicate:predicate];
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
NSLog(#"%#", results);
}];
But caught CKException and showed failed message Expected key-path in comparison expression: SUBQUERY(stringArray, $fS, ANY $fS = "targetString").#count != 0
Any idea or something wrong?
Have a look at the documentation about NSPredicates for CloudKit.
https://developer.apple.com/library/ios/documentation/CloudKit/Reference/CKQuery_class/index.html#//apple_ref/swift/cl/CKQuery
As you can see it only allows a subset of what you can do for the full NSPredicate class. Subqueries and aggregates (like the .#count) are not allowed.

Resources