CloudKit Fetch all User records - ios

I would like to fetch all the users that are working in the same public default container for my app.
Is it possible to fetch all user IDs?
When I try to do this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"TRUEPREDICATE"]; // to search for all the records
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Users" predicate:predicate];
[_publicDB performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
if (error) {
// Error handling for failed fetch from public database
NSLog(#"Error:%#", error);
}
else {
// Display the fetched records
NSLog(#"Users: %#", results);
}
}];
I get an error like this:
Error:<CKError 0x7f9c6b7331f0: "Permission Failure" (10/2007); server message = "Can't query system types"; uuid = 9CBA8EB0-D9DC-46B2-BDF4-10C036599642; container ID = "iCloud.com.xxx.xxx.MyApp">
Do you know how can I achieve this? I want to know from a client perspective (iOS) what are the other users that use my app.

The Users recordType is a special recordType which you cannot query. If you want to perform this sort of queries, then you should create your own recordType and insert a record for every user.

Related

How to update data in Parse IOS

I am facing an issue when trying to update data in Parse.
Here is my code that i am using.
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
[query whereKey:#"name" equalTo:#"Username"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *item, NSError *error)
{
if (!error)
{
[item setObject:#500 forKey:"Score"];
}
[item saveInBackgroundWithBlock:^(BOOL succeeded, NSError *itemError)
{
if (!error)
{
NSLog(#"Score is updated");
}
else
{
NSLog(#"Error in updating: %#",error.description);
}
}];
}
else
{
NSLog(#"Error in getting Score: %#",error.description);
}
}];
This code works only when i create a new PFObject and then try to update it.
But,when i exit my app and then try to update the score again,i am unable to update the data.It throws this error..
Error in getting Score: Error Domain=Parse Code=101 "No results matched the query." UserInfo={error=No results matched the query., NSLocalizedDescription=No results matched the query., code=101}
It works again,if i create a new PFObject.
Please help,i am new to Parse and am still try to understand it.
Thank you.
You need to use findObjectsInBackgroundWithBlock instead of getFirstObjectInBackgroundWithBlock as the latter can only be used if there's at least 1 object.
Reference - Parse Questions
You could also use the PFQuery's count method. If the count is >= 1, use getFirstObjectInBackgroundWithBlock, otherwise display a message / handle that case however you'd like.
Other options include storing the objectId of the GameScore object associated with a player on their user object, creating an object without data using the objectId, then fetching it. Or simply use a pointer, but that can do weird things when saving / querying / fetching.

Exception when trying to convert HeartRate from HealthKitStore

I am working on my first iPhone App: a simple app showing the heartRate results from HealthKit in a nice way. My first step is to show the results as a raw text. But unfortunately I'm getting an exception at the following line, telling me: "thread 1 signal SIGABRT". Does someone know, what I did wrong and hint me in a direction?
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
The rest of the code looks like this:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Set up an HKHealthStore, asking the user for read/write permissions. The profile view controller is the
// first view controller that's shown to the user, so we'll ask for all of the desired HealthKit permissions now.
// In your own app, you should consider requesting permissions the first time a user wants to interact with
// HealthKit data.
if ([HKHealthStore isHealthDataAvailable]) {
NSSet *writeDataTypes = [self dataTypesToWrite];
NSSet *readDataTypes = [self dataTypesToRead];
[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: %#. If you're using a simulator, try it on a device.", error);
return;
}
}];
}
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
// Since we are interested in retrieving the user's latest sample
// we sort the samples in descending order by end date
// and set the limit to 1
// We are not filtering the data, and so the predicate is set to nil.
NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
// construct the query & since we are not filtering the data the predicate is set to nil
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:weightType predicate:nil limit:1 sortDescriptors:#[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
// if there is a data point, dispatch to the main queue
if (results) {
dispatch_async(dispatch_get_main_queue(), ^{
HKQuantitySample *quantitySample = results.firstObject;
// pull out the quantity from the sample
HKQuantity *quantity = quantitySample.quantity;
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
_HeartRateResults.text = [NSString stringWithFormat:#"%# lbs", [NSNumberFormatter localizedStringFromNumber:#(usersBeatsPerMinute) numberStyle:NSNumberFormatterNoStyle]];
});
}
}];
// do not forget to execute the query after its constructed
[_healthStore executeQuery:query];}
There was a comment in the documentation ("These samples use count/time units") I didn't quite understand, so I did a little searching and tried it out and was able to get a value I manually put into the Health app using this:
double rate = [mostRecentQuantity doubleValueForUnit:[[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
I haven't seen unitDividedByUnit before. Here's the article I pulled it from.

How can I send a list of array to parse and retrieve it and show it in a UItableView?

I am developing a chat app in iOS .There I need to add friends of the currentUser into an array and then send it to parse and save it there as an array.
As the user should add the their friend only one time and after that I need to do some functionalities there.
So How can I check that the user is already added as friend as well as how can I retrieve the array of friends?
Please if anyone can suggest me something then it will be a great help for me as I am new to iOS and Parse.
You can create a join table on Parse which stores who is friends with who.
With a PFQuery you can then filter it to get only the friends related to a certain person. You could call this table "Friendship", which stores 2 User objects, meaning, they are friends.
Update
You would have a PFObject subclass called Friendship. the Friendship object would hold 2 things, one is the User and the other is the Friend. Both should be PFUser type.
You don't need to go into parse for creating the join table, if you create a new object in code and save it, parse will create a table for it. So if you create the first Friendship object and save it, parse will keep it in it's own table of Friendships.
Saving a Friendship would be just like this:
- (void)addFriend:(PFUser *)friend {
Friendship *friendship = [[Friendship alloc] init];
friendship.user = self.user;
friendship.friend = friend;
[friendship saveInBackground];
}
This is just a simple example.
To fetch a list of friends from the user:
- (void)fetchFriendsList {
//create a query for friendships
PFQuery *friendsQuery = [[PFQuery alloc] initWithClassName:[Friendship parseClassName]];
//filter it to find only friends for the user
[friendsQuery whereKey#"user" equalTo:self.user];
[friendsQuery findObjectsInBackgroundWithBlock:^(NSArray *friendships, NSError *error) {
if (!error) {
//do something with the list of friendships
self.friends = [friendships valueForKey:#"friend"];
//the above line create an array with only the friend value of each Friendship object inside the array
} else {
NSLog(#"error fetching friends: %#", [error description]);
}
}];
}
To find out if the friend is already added/exists:
- (void)friendExists:(PFUser *)friend withCompletionBlock:(void (^)(BOOL))completionBlock {
//create a query for friendships
PFQuery *friendsQuery = [[PFQuery alloc] initWithClassName:[Friendship parseClassName]];
//filter it to find only friends for the user
[friendsQuery whereKey#"user" equalTo:self.user];
[friendsQuery whereKey#"friend" equalTo:friend];
[friendsQuery findObjectsInBackgroundWithBlock:^(NSArray *friendships, NSError *error) {
if (!error) {
//if we get one result here, it means it already exists
BOOL alreadyExists = friendship.count > 0;
if (completionBlock) { //making sure there is a block as parameter
completionBlock(alreadyExists);
}
} else {
NSLog(#"error fetching friends: %#", [error description]);
}
}];
}
You need to have a block in there to treat the results, since making a PFQuery request is not instant.
Also , when adding more whereKey:, they are treated as "AND", which means, it will only return objects that comply with all the whereKeys.
That's basically it. I assume you know how to subclass the PFObject, if not, you can take a look at the documentation.

CloudKit: creatorUserRecordID of CKRecord Issue (or Bug?)

After iOS 8.3, if the record is created by the current account, its creatorUserRecordID will be like
CKRecordID: [Some Address]; defaultOwner:(_defaultZone:defaultOwner)
And then if fetch this recordID using fetchRecordWithID:completionHandler: from CKDatabase, it will always return error like
CKError [Some Address]: "Unknown Item" (11/2003); server message = "Record not found"; uuid = [Some UUID]; container ID = [Some Container ID]
I never encounter this issue before.
Is it a bug, or should I fetch record from recordID like this ( defaultOwner ) in other way?
EDIT (add sample code)
- (void)postMoodFeed:(NSString *)moodFeed
{
CKRecord *moodRecord = [[CKRecord alloc] initWithRecordType:#"Mood"];
moodRecord[#"moodFeed"] = moodFeed
[[[CKContainer defaultContainer] publicCloudDatabase] saveRecord:moodRecord completionHandler:^(CKRecord *record, NSError *error) {
[self queryMyMood];
}];
}
- (void)queryMyMood
{
// currentUserRecordID is fetched from fetchUserRecordIDWithCompletionHandler: of CKContainer
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"creatorUserRecordID = %#", currentUserRecordID];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Mood" predicate:predicate];
[[[CKContainer defaultContainer] publicCloudDatabase] performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
if (results) {
for (CKRecord *eachRecord in results) {
// Following logs are all __defaultOwner__
NSLog(#"%#", eachRecord.creatorUserRecordID.recordName);
[[[CKContainer defaultContainer] publicCloudDatabase]fetchRecordWithID:eachRecord.creatorUserRecordID completionHandler:^(CKRecord *record, NSError *error) {
// All following logs are "Unknown item" error
NSLog(#"%#", error);
}];
}
}
}];
}
EDIT July 2, 2015
That is a bug.
After reporting to Apple, they fixed this issue in iOS 9 Beta 2.
Indeed it looks like new functionality.
What you could do is first testing if the eachRecord.creatorUserRecordID.recordName == "defaultOwner" and if that's the case you could fetch the record for the ID that you got from the currentUserRecordID
But it would be better to not use the creatorUserRecordID for any functionality in your app. You could better add a new CKReference field and always fill it with the currentUserRecordID. Then even if you have a process that migrates data, you would still know who created that record originally.
Maybe this will help: I've found that if you use [CKFetchRecordsOperation fetchCurrentUserRecordOperation] at the beginning of your app's workflow you won't have defaultOwner CKRecordIDs come out of nowhere.

Modify data with CloudKit

I have data I'd like to modify in CloudKit. I've found this question (Saving Modified Data in CloudKit) and it points to CKModifyRecordsOperation, but being new to this I'm looking for more guidance. I'm setting my object like so:
[object setValue:number forKey:#"total"];
If I'm only modifying one record and not all do I still call CKModifyRecordsOperation?
Any clues to how this is done?
I've been using [self.cloudManager saveRecord:object]; but with modifying the record this isn't working.
You can fetch, modify, and save changes you make to individual records.
The code snippet below shows how to fetch an Artwork record, changes the date attribute value, and saves it to the database.
// Fetch the record from the database
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:containerIdentifier] publicCloudDatabase];
CKRecordID *artworkRecordID = [[CKRecordID alloc] initWithRecordName:#"115"];
[publicDatabase fetchRecordWithID:artworkRecordID completionHandler:^(CKRecord *artworkRecord, NSError *error) {
if (error) {
// Error handling for failed fetch from public database
}
else {
// Modify the record and save it to the database
NSDate *date = artworkRecord[#"date"];
artworkRecord[#"date"]; = [date dateByAddingTimeInterval:30.0 * 60.0];
[publicDatabase saveRecord:artworkRecord completionHandler:^(CKRecord *savedRecord, NSError *saveError) {
// Error handling for failed save to public database
}];
}
}];
Consider to read this article for more detailed information.

Resources