Multiple image in parse relationship - ios

i'm trying to create relationship between multiple images to an object. I keep getting following error:
errorcode
All objects in a relation must have object ids
What am i doing wrong? i've tried to follow documentation and adjust it to my example.
relationship code.
homeObject = [PFObject objectWithClassName:#"Homes"];
homeObject[#"userId"] = [PFUser currentUser].objectId;
for (int i = 0; i < [self.assets count]; i++) {
PFObject *object = [PFObject objectWithClassName:#"homeImages"];
ALAsset *asset = [self.assets objectAtIndex:i];
ALAssetRepresentation *representation = asset.defaultRepresentation;
UIImage *fullResolutionImage =
[UIImage imageWithCGImage:representation.fullResolutionImage
scale:1.0f
orientation:(UIImageOrientation)representation.orientation];
NSData *imgData= UIImageJPEGRepresentation(fullResolutionImage,0.0);
PFFile *imageFile = [PFFile fileWithName:#"Image.jpg" data:imgData];
object[#"Image"] = imageFile;
[object saveInBackground];
PFRelation *relation = [homeObject relationforKey:#"imageId"];
[relation addObject:object];
}
[homeObject saveInBackground];

You need to save the file and wait for it to finish saving (in the background), then you can safely attach it to your homeImages object. Next you need to save that object and wait for it to finish saving (in the background), then you can finally add it to the relation and save the parent object.
Since there's no in-built promises support (there are some 3rd party ones you might want to look at), you'll have to use nesting.
Consider creating an array of PFFile objects and using saveAllInBackground:block:. In the block create the homeImages objects and put them in an array, calling save-all again. Lastly in that completion block add them all to the relation and call saveInBackground on your homeObject.

Related

How to download images in order in iOS Parse?

I am trying to set up three NSMutableArray to use in UITableView.
Here is my code:
for (PFObject *object in objects) {
PFUser *user = (PFUser *) object[#"user"];
[ [user objectForKey:#"image"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error)
{
//Add Comment text
[_commentsArray insertObject:[object objectForKey:#"comment"] atIndex:i];
//Add comment Id
[_commentIDArray insertObject:object.objectId atIndex:i];
//Add user image
[_picsArray insertObject:[UIImage imageWithData:data] atIndex:i ];
if (i == [objects count]-1)
{
[self.tableView reloadData];
}
}
else
{
NSLog(#"Errrror == %ld",(unsigned long)[_picsArray count]);
}
i++;
}];
}
In the PFQuery I am ordering it:
[query orderByDescending:#"createdAt"];
But as far as I can understand image in first row is large. So it takes time to download it. So it goes to second loop. Try to download image. Size is small. Download finished. Add to array. Now download for first image is finished. Add to array but to second place. How can manage it so it add items one by one in the order?
Check this:
// initially, add place holder
for (int i=0; i<objects.count; i++) {
[_commentsArray addObject:#""];
[_commentIDArray addObject:#""];
[_picsArray addObject:#""];
}
for (PFObject *object in objects) {
PFUser *user = (PFUser *) object[#"user"];
[ [user objectForKey:#"image"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error)
{
NSInteger orderIndex = [objects indexOfObject:object];
//Replace Comment text
[_commentsArray replaceObjectAtIndex:[object objectForKey:#"comment"] atIndex:orderIndex];
//Replace comment Id
[_commentIDArray replaceObjectAtIndex:object.objectId atIndex:orderIndex];
//Replace user image
[_picsArray replaceObjectAtIndex:[UIImage imageWithData:data] atIndex:orderIndex ];
if (i == [objects count]-1)
{
[self.tableView reloadData];
}
}
else
{
NSLog(#"Errrror == %ld",(unsigned long)[_picsArray count]);
}
i++;
}];
}
Rather than downloading image and create array to populate tableview, you have to just create array of PFObjects and use it with SDWebImage for Asynchronous image downloading without any issue or blocking UI.
I'm guessing that the question is really about not expending effort to download images beyond the scroll position while the visible rows are still being fetched.
The solution to that problem is to load images lazily, when they're needed to configure a cell in cellForRowAtIndexPath:. There's plenty of generic content available about this idea. For a parse-specific solution, see PFImageView here.
The gist is that image view will take care of loading and caching an image from the file. It will do this asynchronously, so there will be a low perceived lag. Just give the file to the image view and let it do the rest...
Your cellForRowAtIndexPath will look something like this:
// just guessing that your "objects" array is the table's datasource
PFObject *object = self.objects[indexPath.row];
PFUser *user = (PFUser *) object[#"user"];
// put a PFImageView in the cell (in this case with a tag==32)
PFImageView *imageView = (PFImageView *)[cell viewWithTag:32];
imageView.image = [UIImage imageNamed:#”placeholder.png”];
imageView.file = [user objectForKey:#"image"]; // assuming this is a file attribute
[imageView loadInBackground];
You have a problem that you try to do order based adding, where your blocks fire asynchronously so it can be in random order.
You should change to a dictionary or any other keyed data structure and use keys for your comments and pics (e.g. use comment id as the key).
Also double check if the callback of the block is executed on the main queue or any serial queue, because if it's not you need to add locks.
I had the same problem, my images were downloaded but not appearing in the order it should, my table view images and the titles were not matching.
To solve that, I created a column at my class in Parse.com that hold exclusively nameForImages, then each downloaded image is saved using this name.
The nameForImages had to be the same used for the column title, for example:
Title = Pepperoni and Four Cheese | nameForImage =
PepperoniAndFourCheese
Title - Muzzarella and Spinach | nameForImage = MuzzarellaAndSpinach
Etc...
This trick fit to solve my problem because the name of the image and the title appearing in the cell were short and had no special caracters.
I hope it helps or light a solution, good luck.

Error adding objects to a PFRelation, how is this done?

I am trying to get my head around PFRelation in parse. I have a class called "girlBio" that stores information about girls and a class called "stuff" that stores information about items. code below:
PFObject *item = [PFObject objectWithClassName:#"stuff"];
item[#"Name"] = #"PS3";
PFObject *girl = [PFObject objectWithClassName:#"girlBio"];
girl[#"Name"] = #"Jessica";
PFObject *girl2 = [PFObject objectWithClassName:#"girlBio"];
girl2[#"Name"] = #"Cindy";
PFRelation *relation = [item relationForKey:#"owners"];
[relation addObject:girl];
[relation addObject:girl2];
[item saveInBackground];
--------------------------------- update also tried this -------------------------
PFObject *item = [PFObject objectWithClassName:#"stuff"];
item[#"Name"] = #"PS3";
PFObject *girl = [PFObject objectWithClassName:#"girlBio"];
girl[#"Name"] = #"Jessica";
[item saveInBackground];
[girl saveInBackground];
PFRelation *relation = [item relationForKey:#"owners"];
[relation addObject:girl];
[item saveInBackground];
So I want this item to be owned by several girls however when I run the program I get this error:
Error: can't add a non-pointer to a relation (Code: 111, Version: 1.6.0)
Can someone help please?
Thank you
You need to save your objects girl1 and girl2 before saving the relationship. Otherwise, even thought your local copy knows about them, the server does not.
UPDATE
You also need to make sure the saves for girl1 and girl2 and item complete before you save the relation. However, you probably don't want to run these saves on the primary thread, so I'd recommend something like this (which I just ran without issue):
dispatch_async(dispatch_get_main_queue(), ^{
PFObject *item = [PFObject objectWithClassName:#"stuff"];
item[#"Name"] = #"PS3";
PFObject *girl = [PFObject objectWithClassName:#"girlBio"];
girl[#"Name"] = #"Jessica";
[item save];
[girl save];
PFRelation *relation = [item relationForKey:#"owners"];
[relation addObject:girl];
[item saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
//Do something after the last save...
}];
});

Save array while it is being enumerated

I want to save an array to the NSUserDefaults every time a user interacts with the rows in a tableview (deletes them, for instance)
However, I'm encountering the following issue:
Imagine the following use case:
User deletes row -> row is deleted from datasource, datasource is saved to disk
User deletes another row -> row is deleted from datasource, app crashes because datasource of the first action is still being enumerated
How do I solve this problem? It's important that the app has live feedback to the user, so any checks that the user has to wait for are not preferred.
This is the code that I use to save the datasource to the disk:
if(allowSavePaymentArray){
dispatch_async(kAsyncQueue, ^{
allowSavePaymentArray = NO;
__block int loopCount = 0;
NSMutableArray *archiveArray = [NSMutableArray arrayWithCapacity:dataSourceArray.count];
for (NSMutableDictionary *object in dataSourceArray) {
// PFFile isn't easy to encode, but UIImage is, so whenever we encounter a PFFile, we convert it to UIImage
id imageFile = [payment objectForKey:#"img"];
if([imageFile isKindOfClass:[PFFile class]]){
[imageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:imageData];
[payment setObject:image forKey:#"img"]; // the PFFile is now replaced for an UIImage
NSData *paymentEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:payment];
[archiveArray addObject:paymentEncodedObject];
loopCount++;
if(loopCount == [paymentArray count]){ // when done looping, save it all
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
[userData setObject:archiveArray forKey:#"payments"];
allowSavePaymentArray = YES;
}
}
}];
} else {
loopCount++;
NSData *paymentEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:payment];
[archiveArray addObject:paymentEncodedObject];
if(loopCount == [paymentArray count]){ // when done looping
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
[userData setObject:archiveArray forKey:#"payments"];
allowSavePaymentArray =YES;
}
}
}
});
}
Your issue is that you are using fast enumeration on a background thread and you have the likely case that the array being enumerated on the background thread will be modified on the main (or some other) thread.
The fix is simple enough. Create a copy of the array:
__block int loopCount = 0;
NSMutableArray *archiveArray = [NSMutableArray arrayWithCapacity:dataSourceArray.count];
NSArray *tempArray = [dataSourceArray copy]; // make a copy
for (NSMutableDictionary *object in tempArray) {
You may use a serial queue, this will execute one process at the time.
dispatch_queue_t kAsyncQueue;
kAsyncQueue = dispatch_queue_create("com.example.MyQueue", NULL);
Here the documentation from apple:
https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_queue_create
When you finished working with your array on your background queue, spin off the block on main thread to make sure that you have everything going in certain order.

Parse - saving related objects

I'm using Parse for the back end in my project.
As you would imagine there are quite a few relations in the data model. A lot of the time I create a "parent" object and all of its "children" at the same moment and save them all to Parse.
Now, when doing this is it necessary to save the children individually? The same for files etc...
First example - Adding an avatar to a user object
UIImage *image = // image from camera
NSData *pngData = UIImagePNGRepresentation(image);
PFFile *imageFile = [PFFile fileWithData:pngData];
[[PFUser currentUser] setObject:imageFile forKey:"avatar"];
OK, so on the device I can reference the #"avatar" key on the user and get the avatar file. But how should this be saved to Parse?
If I do...
[[PFUser currentUser] saveInBackground];
Will this save the new file that was added? Or do I need to save the file first and wait for this to succeed before then adding it into the user object and then saving the user object?
Second example
Creating a tree of objects...
PFObject *family = [PFObject objectWithClassName:#"Family"];
[family setObject:#"Smith" forKey:#"familyName"];
PFObject *person1 = [PFObject objectWithClassName:#"Person"];
[person1 setObject:#"Bob" forKey:#"name"];
PFObject *person2 = [PFObject objectWithClassName:#"Person"];
[person2 setObject:#"Alice" forKey:#"name"];
PFObject *person3 = [PFObject objectWithClassName:#"Person"];
[person3 setObject:#"Chris" forKey:#"name"];
[family setObject:#[person1, person2, person3] forKey:#"members"];
How would I save this collection of objects?
Can I just do [family saveInBackground];?
Or do I have to go through a process of saving every Person object first and checking that it worked before saving the family object?
As long as the relationship between parent and child is Pointer, you don't have to save the child first. PFRelation works differently, but a save on the parent object will also save children related as pointers. This is true for Cloud Code, and I am pretty sure it holds true for the device as well.
Some details in this answer: https://www.parse.com/questions/cloud-code-efficient-hierarchy-saving
Yes that will do...in fact there is a wide range of methods for saving the contents
Saving an Object to Parse
– save
– save:
– saveInBackground
– saveInBackgroundWithBlock:
– saveInBackgroundWithTarget:selector:
– saveEventually
– saveEventually:
Saving Many Objects to Parse
+ saveAll:
+ saveAll:error:
+ saveAllInBackground:
+ saveAllInBackground:block:
+ saveAllInBackground:target:selector:
Refer Here for more

How can I save an array using Parse?

I am struggling to use Parse to save an array of data - or at least I am finding I can't verify that my data is being saved.
I have a PFObject (journey) and I want to update it with my location (a string in the format: #"{lat, long}").
Eventually though I want to update and save my location every 10-30 seconds meaning I don't want to create a new object each time rather I want to add an array to the object and then update the array each time I have a new location.
Currently my code looks like this:
- (IBAction)startJourneyButtonPressed:(id)sender {
PFObject * journey = [PFObject objectWithClassName:#"Journey"];
journey[#"company"] = _companyNameTextField.text;
journey[#"destination"] = _destinationTextField.text;
[journey saveInBackground];
NSData * faceImageData = UIImagePNGRepresentation([_faceButton imageForState:UIControlStateNormal]);
PFFile * faceImageFile = [PFFile fileWithName:#"faceImage.png" data:faceImageData];
[faceImageFile saveInBackground];
NSData * carImageData = UIImagePNGRepresentation([_carButton2 imageForState:UIControlStateNormal]);
PFFile * carImageFile = [PFFile fileWithName:#"carImage.png" data:carImageData];
[carImageFile saveInBackground];
journey[#"faceImage"] = faceImageFile;
journey[#"carImage"] = carImageFile;
[journey saveInBackground];
NSMutableArray * locationArray = [NSMutableArray new];
[locationArray addObject:[self getCurrentLocationString]];
journey[#"location"] = locationArray;
[journey saveInBackground];
}
The strings are saving fine and so are the images but the dataBase doesn't seem to be getting uploaded with the array.
Here is a screenshot of my database:
I am expecting to see a "location" heading but obviously it isn't appearing.
Reading up on other forums it says that I have to have an object in JSON format for this to work but as I am only storing Strings and I am not receiving and JSON errors I don't think this is the issue.
Any help would be very appreciated
Probably it is saving your array, you should just manually in your database add column with name "location" (do not forget to set it as array type) to see the values.

Resources