Cannot store UIImage in NSMutableDictionary - ios

I'm obtaining an image from Parse server, but when I receive it and store it in an NSMutableDictionary, it fails to stay there when I check it.
NSString* messageSenderId = [receivedMessage objectForKey:#"Sender Id"];
NSLog(#"messageSenderId = %#", messageSenderId);
if([self.avatarDictionary objectForKey:messageSenderId] == nil) { // avatar image for sender not yet stored
PFQuery* userQuery = [PFUser query];
[userQuery whereKey:#"objectId" equalTo:messageSenderId];
PFUser* messageSender = (PFUser *)[[messageSenderQuery findObjects] firstObject];
PFFile *avatarImage = messageSender[#"profilePictureSmall"];
[avatarImage getDataInBackgroundWithBlock:^(NSData * _Nullable data, NSError * _Nullable error) {
UIImage* storedImage = [UIImage imageWithData:data];
[self.avatarDictionary setObject:storedImage forKey:messageSender.objectId];
NSLog(#"objectForKey: %#", [self.avatarDictionary objectForKey:messageSender.objectId]);
[self.collectionView reloadData];
}];
}
When I check for the storedImage variable I can see that it is not empty. But, the NSLog reveals that there is no object for that key after I store it inside.

Sure that you can, but I would not.
Your problem can be due to the fact that you didn't initialize the mutable dictionary.
Images take a lot of memory and if you are storing a lot of images your app can be killed by the system.
A better approach could be save images locally and store the path to them in the mutable dictionary.
Or if you still want to store images in RAM, you can use NSCache. NSCache basically is a mutable dictionary, but it has a better memory management by evicting automatically resources and it is thread safe.

Related

How to send images on Parse without using a query?

Is there a way to directly send a list of users an image using parse on Xcode? I am able to send the image using a query, but I'm worried that searching through every image that is stored in my class, matching it to the user that sent it, and then retrieving that image is going to take a while and I would like it to be there quickly.
This is the way I am currently saving the images to the database:
PFUser *currentUser = [PFUser currentUser];
NSString * username = currentUser.username;
NSData * imageData = UIImageJPEGRepresentation(myImageView.image, 1.0f);
PFFile * newImageFile = [PFFile fileWithName:#"image.jpeg" data:imageData];
PFObject * newImage = [PFObject objectWithClassName:#"Images"];
[newImage setObject:newImageFile forKey:#"imageFile"];
[newImage setObject:name forKey:#"username"];
[newImage setObject:object forKey:#"sendImageToFollowing"];
[newImage saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {}
Saving the image to Parse.com and sending a push that references that image in the push's payload is the way to go, for two reasons:
It's impossible to send more than a few kb via a push message. They are very small by design, so you have to pass a reference to something.
I'm guessing you want the image saved anyways :)
Save the image, get the objectId from Parse, send that as part of the custom push notification payload. You can create a custom dictionary and send it with your push. For more on that, see here:
https://www.parse.com/docs/ios/guide#push-notifications-customizing-your-notifications.
If you want to get the objectId very quickly, you can get it from the query itself when it succeeds:
[newImage saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
//Get the image objectId here
NSString *objectIdString = newImage.objectId;
}

Saving an retrieving sound on Parse.com

I am trying to download some short sound file on Parse.com in an iOS application.
The file has previously been saved using the following code:
NSData *soundData = [NSData dataWithContentsOfURL:myURL];
parse_Sound = [PFFile fileWithName:#"XXYYZZ"
data:soundData];
[parse_Sound saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!succeeded) {
NSLog(#"sound-upload NG”);
} else {
NSLog(#"sound-upload OK");
}];
}
}];
Seeing the message on the debugging console, it appearently works.
Now what kind of code do I need to run to retrieve(download) the sound file?
I have browsed the net, but found nothing clear and working.
To get data back from the server you need to need to run a query asking for that object but you haven't associated the uploaded file with a column in any Class yet. Uploading a PFFile is iOS is a two step process:
1) Upload the PFFile to the server
2) In the callback associated the PFFile with a column in a data object
NSData *soundData = [NSData dataWithContentsOfURL:myURL];
parse_Sound = [PFFile fileWithName:#"XXYYZZ"
data:soundData];
[parse_Sound saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!succeeded) {
NSLog(#"sound-upload NG”);
} else {
NSLog(#"sound-upload OK");
PFObject *soundStuff = [PFObject objectWithClassName:#"Sounds"];
soundStuff[#"soundFile"] = parse_Sound;
[soundStuff saveInBackground];
}];
}
}];
Now to get the data back you would run a query on the Sounds class that will have the sound data in the soundFile column:
PFQuery *query = [PFQuery queryWithClassName:#"Sounds"];
[query whereKey:#"someKey" equalTo:#"someValue"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
PFFile *soundFile = object[#"soundFile"];
NSData *soundData = [soundFile getData];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
I haven't tested any of this code but it at least demonstrates the steps needed and should get you started.
Here are the examples from the documentation:
PFObject *jobApplication = [PFObject objectWithClassName:#"JobApplication"]
jobApplication[#"applicantName"] = #"Joe Smith";
jobApplication[#"applicantResumeFile"] = file;
[jobApplication saveInBackground];
Then to get the data back:
PFFile *applicantResume = anotherApplication[#"applicantResumeFile"];
NSData *resumeData = [applicantResume getData];
Notice that file is being associated with the applicantResumeFile column of the JobApplication class so that the file data can be retrieved in queries.
You need to keep a reference to that file somewhere (ideally in a column of the PFObject it belongs to).
If you don't keep a reference you're out of luck and you can't retrieve already uploaded files that have no association to any object.
I suggest you read through the Parse documentation for files on iOS https://www.parse.com/docs/ios_guide#files/iOS

Caching PFFile data from Parse

My app is a messaging style app and in it you can "tag" another user. (A bit like twitter).
Now, when this message is displayed, the avatar belonging to the person(s) who was tagged is displayed with that message.
The avatar of the user is stored as a PFFile against the PFUser object.
I'm loading it something like this...
PFImageView *parseImageView = ...
[taggedUser fetchIfNeededInBackgroundWithBlock:^(PFObject *user, NSError *error) {
parseImageView.file = user[#"avatar"];
[parseImageView loadInBackground];
}];
This all works fine.
The load if needed part of the code will most of the time not touch the network as for the majority of the time it has the user data cached.
However, the load in background part that gets the image and puts it into the image view runs every single time. There doesn't seem to be any caching on the PFFile data at all.
Even after downloading the same user's avatar numerous times it still goes to the network to get it.
Is there a way to get this data to cache or is this something I'll have to implement myself?
PFFile will automatically cache the file for you, if the previous PFQuery uses caching policy such as:
PFQuery *query = [PFQuery queryWithClassName:#"MyClass"];
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
To check whether the PFFile is in local cache, use:
#property (assign, readonly) BOOL isDataAvailable
For example:
PFFile *file = [self.array objectForKey:#"File"];
if ([file isDataAvailable])
{
// no need to do query, it's already there
// you can use the cached file
} else
{
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error)
{
if (!error)
{
// use the newly retrieved data
}
}];
}
Hope it helps :)
In the end I created a singleton with an NSCache and queried this before going to Parse.
Works as a quick stop for now. Of course, it means that each new session has to download all the images again but it's a lot better now than it was.
You can cache result of PFQuery like below code..And need to check for cache without finding objects in background everytime..while retrieving the image.It has some other cache policies also..Please check attached link also..
PFQuery *attributesQuery = [PFQuery queryWithClassName:#"YourClassName"];
attributesQuery.cachePolicy = kPFCachePolicyCacheElseNetwork; //load cache if not then load network
if ([attributesQuery hasCachedResult]){
NSLog(#"hasCached result");
}else{
NSLog(#"noCached result");
}
Source:https://parse.com/questions/hascachedresult-always-returns-no
Hope it helps you....!

How to save a basic Core Data store to Parse.com?

I have looked and looked in Parse docs, SO and Google, and can not find an example of storing a plain ol' Core Data SQLite file to Parse.com. Initially I just want to store the Core Data file as a backup; eventually I want to add FTASync and then ability for others to utilize the stored Core Data file from this iOS app.
Is there an example of doing this without using a PFObject? Can someone point me to a place in the Parse docs where I can find out how to do this?
No, you cannot do this without any PFObject. Theoretically you can save backups just with
- (void)createBackupFromSQLiteStorageAtPath:(NSString*)path
{
NSString *name = [[NSDate date] description]; // for example, stringified date will act as name
PFFile *backup = [PFFile fileWithName:name contentsAtPath:path];
[backup saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
if (error)
{
// handle
}
else
{
// success
}
}];
}
But! If you want to access it from parse's fileserver you'll need to keep PFFile objects somehow (you can also store PFFile's url property - but it's hack) - and here's the case where PFObject comes to help. Assuming you have backed up your store already:
- (void)storeBackupFile:(PFFile*)file
{
PFObject *backup = [PFObject objectWithClassName:#"Backup"];
[backup setObject:file forKey:#"file"];
[backup setObject:[PFUser currentUser] forKey:#"user"];
[backup saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
if (error)
{
[backup saveEventually];
}
else
{
// success
}
}];
}
So after this you'll have Backup object in parse database, with link to backup file and user that created backup.
Some more considerations:
1) It's good to organize such backup as NSOperation subclass.
2) It's bad idea to store backups with Parse in such way. File storage on Parse is very expensive resource. Also, PFFile has local cache - your storage will be duplicated each time you make backup, so app's size will increase dramatically with often backups.

Downloading images with parse causes unrecognized selector error

I am trying to download images from Parse and when I am finished downloading all images from parse my program crashes with the error:
NSNull getDataInBackgroundWithBlock:progressBlock:]: unrecognized selector sent to instance.
I have tried adding some conditions such as
if(![object objectForKey:#"goal_image"]) //If it is nil => exit
But it is still crashing. Here is my code:
PFQuery *query = [PFQuery queryWithClassName:#"Goal"];
[query getObjectInBackgroundWithId:[object objectId] block:^(PFObject *object, NSError *error) {
if(!error)
{
PFFile * imageFile = [object objectForKey:#"goal_image"];
if (imageFile) {
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error2) {
if (!error2) {
NSString *jpgPath = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"Images/Goals/%#",[object objectId]]];
[data writeToFile:jpgPath atomically:YES];
}
} progressBlock:^(int percentDone) {
if (percentDone == 100) {
NSLog(#"Download Completed");
}
}];
}
}
}];
tl;dr
You should check for NSNull aside from checking for nil.
PFFile * imageFile = object[#"goal_image"]; // note the modern Obj-C syntax
if (imageFile && ![image isEqual:[NSNull null]]) {
...
}
Explanation
NSNull is different from nil, the first one being an object.
Since NSArray and NSDictionary only hold objects, nil cannot be stored in such containers and that's why NSNull is used, typically to represent a null value returned in the JSON response.
Sending messages to nil fails silently, whereas sending an unrecognized selector to the NSNull singleton will result in a crash.
Also remember that objectForKey: will return nil in case the key is not found in the dictionary.
I know this is old, but just in case anyone else is having this problem. For me, the above answer didn't work. What worked for me was enabling local datastore in app delegate. Honestly, not sure why this works, but I thought I should share
// Enable Local Datastore
[Parse enableLocalDatastore];

Resources