I have a module that saves data based on user inputs from text fields and adds it to an array saved in Parse. Testing with pre-populated data the module works fine. However, if I go to add data where there previously wasn't any it causes the app to ignore the saving element all together. What is the best way to add new arrays to Parse where no previous data existed? Here is the existing code:
- (IBAction)saveButton:(id)sender
{
[getData showGlobalProgressHUDWithTitle:#"Saving Test"];
PFQuery *tankQuery = [PFQuery queryWithClassName:#"WaterTests"];
[tankQuery whereKey:#"tankObjectId" equalTo:_passedValue];
[tankQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error)
{
for (PFObject *object in objects)
{
calciumArray = [object valueForKey:#"calciumArray"];
nitrateArray = [object valueForKey:#"nitrateArray"];
phosArray = [object valueForKey:#"phosphateArray"];
salinityArray = [object valueForKey:#"salinityArray"];
alkArray = [object valueForKey:#"alkArray"];
phArray = [object valueForKey:#"phArray"];
tempArray = [object valueForKey:#"tempArray"];
[calciumArray addObject: addCalcium.text];
[nitrateArray addObject: addNitrate.text];
[phosArray addObject: addPhosphate.text];
[salinityArray addObject: addSalinity.text];
[alkArray addObject: addAlk.text];
[phArray addObject: addPH.text];
[tempArray addObject: addTemp.text];
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error)
{
NSLog(#"Something happened");
[getData dismissGlobalHUD];
[self hideTestView];
}
else
{
NSLog(#"Nothing happened: %#", error);
}
[self resignFirstResponder];
[_chartView reloadData];
}];
}
}
}];
}
I know that I'm close, but don't quite understand why it's not firing off the saving code. I believe its because I'm trying to retrieve objects that are empty first, but I could be wrong about that. Anyone have any advice for this?
UPDATE Here is the ViewDidLoad Code as it pertains to retrieving the data.
- (void)viewDidLoad
{
self.title = #"Water Quality";
statQuery = [PFQuery queryWithClassName:#"WaterTests"];
[statQuery whereKey:#"tankObjectId" equalTo:_passedValue];
[statQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error)
{
for (PFObject *object in objects)
{
[statQuery whereKey:#"tankObjectId" equalTo:_passedValue];
calciumArray = [object valueForKey:#"calciumArray"];
_footerView.sectionCount = calciumArray.count;
NSLog(#"CALCIUM ARRAY: %#", calciumArray);
nitrateArray = [object valueForKey:#"nitrateArray"];
NSLog(#"NITRATE ARRAY: %#", nitrateArray);
phosArray = [object valueForKey:#"phosphateArray"];
NSLog(#"PHOSPHATE ARRAY: %#", phosArray);
salinityArray = [object valueForKey:#"salinityArray"];
NSLog(#"SALINITY ARRAY: %#", salinityArray);
alkArray = [object valueForKey:#"alkArray"];
NSLog(#"ALKALINITY ARRAY: %#", alkArray);
phArray = [object valueForKey:#"phArray"];
NSLog(#"PH ARRAY: %#", phArray);
tempArray = [object valueForKey:#"tempArray"];
NSLog(#"TEMPERATURE ARRAY: %#", tempArray);
}
}
if (calciumArray == nil || [calciumArray count] == 0)
{
NSLog(#"You should probably fire off the new test function here.");
[_chartView setUserInteractionEnabled:NO];
[self newTest:self];
NSLog(#"Error: %#", error);
}
}];
This is the only other time the data is mentioned at all in the view. I allocate and initialize the arrays elsewhere, but I don't think that's the problem considering existing arrays pull and save just fine.
(PFObject *object in objects)
{
calciumArray = [object valueForKey:#"calciumArray"];
.
.
. //Other object for array
[arrayMu] [calciumArray addObject: addCalcium.text]; //Add in NSMutableArray
.
.
.
[objects save]; //Instead of saveinbackground.
}
Try this out. Also do reply back in time as after a long time I had would have to read all your query again as not in touch with it.
UPADTE...
-(IBAction)updateButton:(id)sender
{
PFQuery *query = [PFQuery queryWithClassName:#"UserDetail"];
NSString *str =self.nameTextField.text;
[query whereKey:#"name" containsString:str];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error)
{
if (!error)
{
NSLog(#"Successfully retrieved: %#", [object objectId]); //I retrieve the object succesfully
object[#"job"] = self.jobTextField.text; //Made changes to it.
object[#"hobby"] = self.hobbyTextField.text;
[object saveInBackground]; //Again saved it in background
}
else
{
NSLog(#"Error: %#", [error localizedDescription]);
} }];
}
Then I checked my parse db and my data was modified.
I was able to properly add a new array from a single object if nothing else was available by plainly adding a new object to Parse:
- (IBAction)saveButton:(id)sender
{
[getData showGlobalProgressHUDWithTitle:#"Saving Test"];
PFQuery *tankQuery = [PFQuery queryWithClassName:#"WaterTests"];
[tankQuery whereKey:#"tankObjectId" equalTo:_passedValue];
PFObject *testObject = [[PFObject alloc] initWithClassName:#"WaterTests"];
[tankQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects.count == 0)
{
[testObject setObject:_passedValue forKey:#"tankObjectId"];
[testObject addObject:addCalcium.text forKey:#"calciumArray"];
[testObject addObject:addNitrate.text forKey:#"nitrateArray"];
[testObject addObject:addPhosphate.text forKey:#"phosphateArray"];
[testObject addObject:addSalinity.text forKey:#"salinityArray"];
[testObject addObject:addAlk.text forKey:#"alkArray"];
[testObject addObject:addPH.text forKey:#"phArray"];
[testObject addObject:addTemp.text forKey:#"tempArray"];
[testObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error)
{
[getData dismissGlobalHUD];
[self hideTestView];
NSLog(#"Something, somewhere, saved.");
}
else
{
NSLog(#"HOORAY ERROR: %#", error);
}
[_chartView reloadData];
}];
}
However, I also had to check to see if there were any objects to begin with. If there were not any, simply add them in new. If there were, I had to run the code to query the database and add a for loop for every object that was found with the given criteria:
else if (objects.count != 0)
{
for (PFObject *object in objects)
{
[object addObject:addCalcium.text forKey:#"calciumArray"];
[object addObject:addNitrate.text forKey:#"nitrateArray"];
[object addObject:addPhosphate.text forKey:#"phosphateArray"];
[object addObject:addSalinity.text forKey:#"salinityArray"];
[object addObject:addAlk.text forKey:#"alkArray"];
[object addObject:addPH.text forKey:#"phArray"];
[object addObject:addTemp.text forKey:#"tempArray"];
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
if (!error)
{
[getData dismissGlobalHUD];
[self hideTestView];
[_chartView reloadData];
NSLog(#"Here we go again");
}
}];
}
}
}];
}
I'm submitting this as my own answer and offering this to the community wiki to help others figure out similar problems. Problem: SOLVED!
Related
I succeed to add friend with Cloud Code and Parse.com.
Now I would like to delete friend relation with Cloud Code in didSelectRowAtIndexPath
My error is "attempt to insert nil object from objects[0]'"
But I don't know what parameters I need to configure, I found the cloud code for main.js :
Parse.Cloud.define("removeFriend", function(request, response)
{
// here's how to get the client's user making the request
var user = request.user;
// consider checking that user && request.params.friend are valid
// if not, return response.error("missing user or friend id")
getUser(request.params.friend).then(function(friend) {
// your code prematurely called response.success() here, thereby canceling any further steps
friend.relation("friendsRelation").remove(user);
// return the promise returned by save() so we can chain the promises
return friend.save();
}).then(function(result) {
// only now that the save is finished, we can claim victory
response.success(result);
}, function (error) {
response.error(result);
});
});
// EDIT - the OP once referred to a getUser function that we assume to be something like this:
// return a promise to get a user with userId
function getUser(userId) {
var userQuery = new Parse.Query(Parse.User);
return userQuery.get(userId);
}
Here is my code EditFriends.m :
- (void)viewDidLoad
{
[super viewDidLoad];
PFQuery *query = [PFUser query];
[query orderByAscending:#"name"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
else {
self.allUsers = objects;
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
}];
self.currentUser = [PFUser currentUser];
[self loadFriends];
}
-(void) loadFriends{
self.friendsRelation = [[PFUser currentUser] objectForKey:#"friends"];
PFQuery *query = [self.friendsRelation query];
[query orderByAscending:#"username"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (error) {
NSLog(#"Error %# %#", error, [error userInfo]);
}
else {
self.friends = objects;
[self.tableView reloadData];
}
}];
}
- (BOOL)isFriend:(PFUser *)user {
for(PFUser *friend in self.friends) {
if ([friend.objectId isEqualToString:user.objectId]) {
return YES;
}
}
return NO;
}
CellForRowAtIndexPath :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
PFUser *user = [self.allUsers objectAtIndex:indexPath.row];
NSString *name = [[self.allUsers objectAtIndex:indexPath.row] valueForKey:#"username"];
cell.textLabel.text = name;
if ([self isFriend:user]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
didSelectRowAtIndexPath :
PFUser *selected = [self.allUsers objectAtIndex:indexPath.row];
if ([self isFriend:selected]) {
NSLog(#"déjà amis");
// PFObject *friendRequest = [self.friendRequests objectAtIndex:indexPath.row];
[PFCloud callFunctionInBackground:#"removeFriend" withParameters:#{#"friendRequest" : selected.objectId} block:^(id object, NSError *error) {
if (!error) {
//add the fromuser to the currentUsers friends
//save the current user
[self.currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
} else {
}
}];
}
else {
}
}];
}
else{
PFUser *selectedUser = [self.allUsers objectAtIndex:indexPath.row];
//request them
PFObject *friendRequest = [PFObject objectWithClassName:#"FriendRequest"];
friendRequest[#"from"] = self.currentUser;
friendRequest[#"fromUsername"] = [[PFUser currentUser] objectForKey:#"username"];
//selected user is the user at the cell that was selected
friendRequest[#"to"] = selectedUser;
// set the initial status to pending
friendRequest[#"status"] = #"pending";
[friendRequest saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Yay" message:#"Friend request sent" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
} else {
// error occurred
}
}];
}
The iOS code looks okay, and only needs to be sure it sends the correct user objectId.
The cloud code is close, but must be improved a little:
Parse.Cloud.define("removeFriend", function(request, response)
{
// here's how to get the client's user making the request
var user = request.user;
// consider checking that user && request.params.friend are valid
// if not, return response.error("missing user or friend id")
getUser(request.params.friendRequest).then(function(friend) {
// your code prematurely called response.success() here, thereby canceling any further steps
console.log("relation is:" + JSON.stringify(friend.relation("friends")));
friend.relation("friends").remove(user);
// return the promise returned by save() so we can chain the promises
return friend.save();
}).then(function(friend) {
// friendship goes both ways, so remove the friend from user's friends
user.relation("friends").remove(friend);
return user.save();
}).then(function(result) {
// only now that the save is finished, we can claim victory
console.log("relation is:" + JSON.stringify(result.relation("friends")));
response.success(result);
}, function (error) {
response.error(error);
});
});
// EDIT - the OP once referred to a getUser function that we assume to be something like this:
// return a promise to get a user with userId
function getUser(userId) {
var userQuery = new Parse.Query(Parse.User);
return userQuery.get(userId);
}
EDIT - to call:
PFUser *selected = [self.allUsers objectAtIndex:indexPath.row];
if ([self isFriend:selected]) {
NSLog(#"déjà amis");
[PFCloud callFunctionInBackground:#"removeFriend" withParameters:#{#"friendRequest" : selected.objectId} block:^(id object, NSError *error) {
// etc.
I have the following code to update my objects that have changed on the server:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[PFCloud callFunctionInBackground:#"getAllData" withParameters:#{#"LastUpdate": [self getLastSyncDate], #"Scan": #"NO"} block:^(NSArray *result, NSError *error){
if (!error) {
for (int x = 0; x < result.count; x++) {
PFObject *object = [result objectAtIndex:x];
[object unpinInBackgroundWithName:object.parseClassName block:^(BOOL succeeded, NSError *error){
if (!error) {
if ([object[#"deleted"] boolValue] != YES) {
[object pinInBackgroundWithName:object.parseClassName block:^(BOOL succeeded, NSError *error){
if (x == result.count - 1) {
[self performSelector:#selector(reloadTableView) withObject:nil afterDelay:0.3];
}
}];
}
}
}];
}
[defaults setObject:[NSDate date] forKey:#"LastSynced"];
[defaults synchronize];
}
[self reloadTableView];
}];
But my objects are not updating. Calling a PFQuery from the local datastore shows me the old versions.
Any idea?
It looks like you have a bunch of background functions nested in each other, and this is causing a racing error.
Here's the order of operations for your code:
Get the data
Iterate through each item for updates and save changes in the background (this is key)
Synchronize
Reload
Just getting to the end of the iteration does not mean that all the saves are complete - You need to wait until all the saves from step 2 are complete before you continue with steps 3 and 4.
A suggested order of operations:
Get the data
Set a global with the number of objects, and another global with the number that have been updated (starts at 0)
Iterate through each item for updates and check with a function to see if all the updates are complete, i.e. do your two global equal each other. **Only if all updates are complete do you sync and reload
Something like this:
//Declare these two globally
int total;
int complete;
complete = 0;
*defaults = [NSUserDefaults standardUserDefaults];
[PFCloud callFunctionInBackground:#"getAllData" withParameters:#{#"LastUpdate": [self getLastSyncDate], #"Scan": #"NO"} block:^(NSArray *result, NSError *error){
if (!error) {
total = result.count;
for (int x = 0; x < result.count; x++) {
PFObject *object = [result objectAtIndex:x];
[object unpinInBackgroundWithName:object.parseClassName block:^(BOOL succeeded, NSError *error){
if (!error) {
if ([object[#"deleted"] boolValue] != YES) {
[object pinInBackgroundWithName:object.parseClassName block:^(BOOL succeeded, NSError *error){
self.total++;
[self updateCompleteCheck];
}];
}
}
}];
}
}
}];
-(void)updateCompleteCheck
{
if(total == complete) {
//Only when this is done do you know, for certain, the update is really complete
[defaults setObject:[NSDate date] forKey:#"LastSynced"];
[defaults synchronize];
[self reloadTableView];
}
}
I have a parse iOS app where I need to search the names of a couple thousand users (~3,000). I am trying to modify my search code so that I can do this but I need help. Right now my code for search looks like this:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.tableView) {
return self.objects.count;
} else {
return self.searchResults.count;
}
}
-(void)filterResults:(NSString *)searchTerm :(int)limit :(int)skip {
[self.searchResults removeAllObjects];
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query whereKey:#"isTeacher" equalTo:#"False"];
[query setLimit: limit];
[query setSkip: skip];
[[[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects.count == limit) {
[self performTeacherQueryWithLimit:limit andSkip:skip+limit];
}
else{
NSArray *results = [NSArray arrayWithArray:objects];
NSLog(#"%#", results);
NSLog(#"%lu", (unsigned long)results.count);
NSLog(#"results^");
[self.searchResults addObjectsFromArray:results];
NSPredicate *searchPredicate =
[NSPredicate predicateWithFormat:#"SELF.name contains[c] %#",searchTerm];
_searchResults = [NSMutableArray arrayWithArray:[results filteredArrayUsingPredicate:searchPredicate]];
[self.searchDisplayController.searchResultsTableView reloadData];
NSLog(#"%#", _searchResults);
NSLog(#"%lu", (unsigned long)_searchResults.count);
NSLog(#"search results^");
}
}];
]]
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self filterResults:searchString];
return YES;
}
This code will not work because I call the filterResults at the bottom without all the correct parameters but that is because I got halfway through and now I am stuck. I know I need to use the setSkip but I'm not sure how to make that work for my searching. Any help would be awesome! Thanks!
I do this sort of thing with a method that handles the query and its results together, like this:
- (void)runQuery:(PFQuery *)query filling:(NSMutableArray *)array completion:(void (^)(BOOL))completion {
query.skip = array.count;
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
[array addObjectsFromArray:objects];
if (objects.count < query.limit) {
return completion(YES);
} else {
[self runQuery:query filling:array completion:completion];
}
} else {
return completion(NO);
}
}];
}
Use it like this:
PFQuery *query = [PFQuery queryWithClassName:#"MyClass"];
// setup query
query.limit = // set this to a reasonable size
// the given method will do ceil(N / limit) finds, where N is the number
// of rows that satisfy the query
NSMutableArray *array = [#[] mutableCopy];
[self runQuery:query filling:array completion:^(BOOL success) {
NSLog(#"%#", array);
// you would do your local search and set search results here
}];
A more functional version would call a progress block in between queries. This would allow you to continuously update results. For that, just add a progress block parameter and call it right after [array addObjectsFromArray:objects];.
i hope NSlog outside of block can have value.
i don't know how to fix it. i hope loadPuppiesFromJSON can working.
could somebody help me!?THX!!
- (id)init
{
if((self = [super init]))
{
allPuppies=[self loadPuppiesFromJSON];
}
return self;
}
- (NSArray *)loadPuppiesFromJSON
{
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
// Do something with the found objects
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
self.allFishes = objects;
NSLog(#"%#",allFishes);<-----here have some value
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"%#",allFishes);<----here doesn't have value
}
The reason your NSLog(#"%#",allFishes); doesn't have a value is that it is being called before your query is complete. It's a Race Condition, and at run-time here is what the program thinks you want:
Start query
Call NSLog outside the block - no data yet, so it's empty
Call completion block when it is ready - this is when the data is generated so you get a populated NSLog here
Since you also want to have the value return, try this instead - just remove your NSLost from outside the block and call a second function with the return value to continue your logic:
- (void)loadPuppiesFromJSON
{
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
// Do something with the found objects
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
}
self.allFishes = objects;
NSLog(#"%#",allFishes);<-----here have some value
[self processPuppiesArray:objects];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
//No NSLog out here - it won't do anything
}
- (void)processPuppiesArray:(NSArray *)puppiesArray
{
//Continue processing here...
}
The NSLog where you are not getting any value is because block is called in background after it completes your function execution. Thats why before executing you block, it finishes your function execution and during that time allFishes variable is empty/nil. Once the block excution starts and assigns value to allFishes after that this variable gets the value.
If you need to execute some code after allFishes have value create a function and call it after blockexecution finishes like below:
- (NSArray *)loadPuppiesFromJSON
{
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
// Do something with the found objects
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
self.allFishes = objects;
NSLog(#"%#",allFishes);<-----here have some value
[self dataLoadFinished];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"%#",allFishes);<----here doesn't have value
}
-(void) dataLoadFinished {
NSLog(#"%#",self.allFishes);<----here you will have value
}
The reason you aren't retrieving any data is because you are trying to retrieve an Array from a function but never return anything.
When you use the -(NSArray *)loadPuppiesFromJSON you actually have to return an Array which you are not doing.
For example:
-(NSArray *)loadPuppiesFromJson {
NSArray *allFishes;
allFishes = #[#"someinfo1",#"someinfo2",#"someinfo3"];
return allFishes; // this is where your getting your error and need for it to return anything
}
I would suggest butting your code into a void function like so:
-(void)loadPuppiesFromJSON {
PFQuery *query = [PFQuery queryWithClassName:#"Information"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.allFishes = objects;
for (PFObject *object in objects) {
Fish *fish = [[Fish alloc]init];
fish.price = object[#"price"];
fish.name = object[#"name"];
[object saveInBackground];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"%#",allFishes);<----here has value now
}
And call it in viewDidLoad or viewDidAppear or wherever you need it with:
[self loadPuppiesFromJSON]
This is my first time doing any work with database persistence/maintenance online, so I apologize in advance for my sloppy code. My app consists of the user creating a bunch of athletes and having their data save online so they can be accessed from any device. This works fantastically, except the athletes each get saved twice online, and it's making me want to rip my hair out. I've checked my code several hundred times, but I just can't seem to find why athletes are being saved twice on the server, resulting in 2 locally as well. I'm utilizing the Parse.com framework. Am I missing something?
The following method gets called from a pull down to refresh on a table view controller.
- (void)getParseData {
NSLog(#"GET PARSE DATA WAS CALLED");
if(self.syncing != TRUE){
NSLog(#"GET PARSE DATA RAN");
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterLongStyle;
PFQuery *query = [PFQuery queryWithClassName:#"Athlete"];
[self populateAthleteArray];
if (self.athleteArray.count == 0) {
NSLog(#"ATHLETE ARRAY IS EMPTY");
// If the athlete array has no objects, download all objects from the database.
[query findObjectsInBackgroundWithBlock: ^(NSArray *objects, NSError *error) {
self.syncing = TRUE;
if (!error) {
self.syncing = FALSE;
for (PFObject * object in objects) {
Athlete *newAthlete = [NSEntityDescription insertNewObjectForEntityForName:#"Athlete" inManagedObjectContext:_managedObjectContext];
newAthlete.first = object[#"first"];
newAthlete.last = object[#"last"];
newAthlete.updatedAt = [dateFormatter stringFromDate:[object updatedAt]];
newAthlete.objectId = [object objectId];
[_managedObjectContext save:nil];
}
self.syncing = FALSE;
}
else {
self.syncing = FALSE;
NSLog(#"Error: %# %#", error, [error userInfo]);
}
if(self.needToUploadArray.count > 0){
[PFObject saveAllInBackground:self.needToUploadArray target:nil selector:#selector(emptyUploadArray)];
}
}];
[self populateAthleteArray];
[self.tableView reloadData];
}
else {
NSLog(#"ATHLETE ARRAY HAS ATHLETES ALREADY");
// Athlete array has athletes already
NSMutableArray *athletesToUpload = [NSMutableArray array];
// Placeholder array for athletes that aren't in the database.
for (Athlete *athlete in athleteArray) {
if (athlete.objectId.length == 0 || athlete.objectId == nil) {
// If the objectId is nil, it wasn't uploaded to the database. Add to placeholder array.
[athletesToUpload addObject:athlete];
}
}
[query findObjectsInBackgroundWithBlock: ^(NSArray *objects, NSError *error) {
self.syncing = TRUE;
if (!error) {
// Downloaded all athletes successfully
self.syncing = FALSE;
BOOL found = FALSE;
[self populateAthleteArray];
NSMutableArray *athletesToDelete = [NSMutableArray array];
for (Athlete * athlete in athleteArray) {
for (PFObject * object in objects) {
// Check to see each local athlete exists in the online database
if ([object.objectId isEqualToString:athlete.objectId]) {
// Athlete was find in the online database
found = TRUE;
break;
}
}
if (found != TRUE) {
NSLog(#"%# was not found online.",athlete.first);
if(athlete.objectId.length > 0){
NSLog(#"%# was deleted online. delete them locally",athlete.first);
[athletesToDelete addObject:athlete];
}
else{
// No athlete in the local database matched any of the athletes online
PFObject *onlineAthlete = [PFObject objectWithClassName:#"Athlete"];
onlineAthlete[#"first"] = athlete.first;
onlineAthlete[#"last"] = athlete.last;
PFFile *imageFile = [PFFile fileWithName:[NSString stringWithFormat:#"%#%#MedicalRelease.jpg", athlete.first, athlete.last] data:athlete.medical_release_image];
onlineAthlete[#"medical_release_image"] = imageFile;
[onlineAthlete saveInBackgroundWithBlock: ^(BOOL succeeded, NSError *error) {
self.syncing = TRUE;
if (succeeded) {
NSLog(#"SAVED SUCCESSFULLY");
self.syncing = FALSE;
PFQuery *query = [PFQuery queryWithClassName:#"Athlete"];
[query orderByDescending:#"createdAt"];
[query getFirstObjectInBackgroundWithBlock: ^(PFObject *object, NSError *error) {
Athlete *athleteToChange = [self findAthlete:athlete.objectId];
[athleteToChange setObjectId:[object objectId]];
[_managedObjectContext save:nil];
}];
}
}];
}
}
found = FALSE;
}
if(athletesToDelete.count > 0){
for(id athlete in athletesToDelete){
NSManagedObject *eventToDelete = athlete;
[_managedObjectContext deleteObject:eventToDelete];
[athleteArray removeObjectAtIndex:[athleteArray indexOfObject:athlete]];
[self.tableView reloadData];
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"there is an error: %#", error);
}
}
}
for (PFObject *object in objects) {
// Loop through every athlete downloaded
for (Athlete * athlete in athleteArray) {
// For every object downloaded, compare it to every athlete in the local database.
if ([object.objectId isEqualToString:athlete.objectId]) {
// If the object's id matches the local athletes id, we found the object
if ([object updatedAt] >= [dateFormatter dateFromString:athlete.updatedAt]) {
// If the object has been updated more recently than the athlete, update the local athlete
Athlete *sameAthlete = [self findAthlete:athlete.objectId];
sameAthlete.first = object[#"first"];
sameAthlete.last = object[#"last"];
sameAthlete.updatedAt = [dateFormatter stringFromDate:[object updatedAt]];
sameAthlete.address = object[#"address"];
sameAthlete.objectId = [object objectId];
[_managedObjectContext save:nil];
}
found = TRUE;
// The athlete was found in the database
break;
}
}
if (found != TRUE) {
// We looped through all the local athletes, the object downloaded isn't in the local database; add them.
Athlete *athlete = [NSEntityDescription insertNewObjectForEntityForName:#"Athlete" inManagedObjectContext:_managedObjectContext];
athlete.first = object[#"first"];
athlete.last = object[#"last"];
athlete.objectId = [object objectId];
athlete.address = object[#"address"];
athlete.updatedAt = [dateFormatter stringFromDate:[object updatedAt]];
[_managedObjectContext save:nil];
}
// Reset flag var
found = FALSE;
}
}
else {
self.syncing = FALSE;
NSLog(#"Error: %# %#", error, [error userInfo]);
}
self.syncing = FALSE;
}];
if (athletesToUpload.count > 0) {
for (Athlete *athlete in athletesToUpload) {
PFObject *upload = [PFObject objectWithClassName:#"Athlete"];
upload[#"first"] = athlete.first;
upload[#"last"] = athlete.last;
PFFile *imageFile = [PFFile fileWithName:[NSString stringWithFormat:#"%#%#MedicalRelease.jpg", athlete.first, athlete.last] data:athlete.medical_release_image];
upload[#"medical_release_image"] = imageFile;
[upload saveInBackgroundWithBlock: ^(BOOL succeeded, NSError *error) {
if (succeeded) {
PFQuery *uploadQuery = [PFQuery queryWithClassName:#"Athlete"];
[uploadQuery orderByDescending:#"createdAt"];
[uploadQuery getFirstObjectInBackgroundWithBlock: ^(PFObject *object, NSError *error) {
[athlete setObjectId:[object objectId]];
}];
}
}];
}
}
[self populateAthleteArray];
[self.tableView reloadData];
}
}
}
In the branch marked by the comment // No athlete in the local database matched any of the athletes online you are creating a new PFObject and saving it to Parse. In my understanding, this should not be correct, since the athlete is not in the local db but online.