I am migrating across my backend from Parse :'( to Firebase and am having some issues understand Firebase's implementation around assets and referencing them.
Essentially I need to have a JSON structure detailing some articles, each of these articles will have an image. Currently the JSON is manually added in order for the app to reference the latest articles. The images for this are manually uploaded to the storage aspect of Firebase and the file name of this is added to the JSON of the articles.
My app calls the JSON in order to populate a table view with a label for the heading of the article and an image in the same cell.
The problem I am having is retrieving the image URL for the storage assets in order to load the image into the tableview -
First I call the data of the article from the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:#"articles"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
NSArray *articles = (NSArray *)snapshot.value;} withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
}];
From the return snapshot is converted into a model object - where the article heading is set to the model object as well as the image URL generated
Article *article = [Article new];
article.title = [articleObj valueForKey:#"title"];
if ([articleObj objectForKey:#"image"] != [NSNull null]) {
// Points to the root reference
FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:#"gs://project-3229518851181635221.appspot.com"];
// Points to "images"
FIRStorageReference *imagesRef = [storageRef child:#"articleImages"];
// Note that you can use variables to create child values
FIRStorageReference *imagePath = [imagesRef child:articleObj[#"image"]];
if (imagePath){
article.imageURL = [NSURL URLWithString:imagePath.fullPath];
// Fetch the download URL
[imagePath downloadURLWithCompletion:^(NSURL *URL, NSError *error){
if (error != nil) {
// Handle any errors
NSLog(#"**ERROR %#", error.description);
NSLog(#"**ERROR %#", URL.absoluteString);
NSLog(#"**ERROR %#",article.title);
} else {
article.imageURL = URL;
}
}];
}
The problem I have with this method (apart from it feeling like a lot of code for something which feels like it should be a standard property) is that the Article title is available immediately, but the 'downloadURL' block takes longer, so I either need to observe when this finishes and not reload my table until each article has finished obtaining its Image URL
SO the question:
Is there an alternative non-blocking way to obtain the image URL
Or is the URL reference I see in the storage portal for the URL reliable enough to use in my JSON instead of a reference to the image name (so store the URL in my DB rather than the image name)
Also for reference, I am using SDWebimage for lazyloading images, but the URL is currently not present the first time the tablereloads due to the above-mentioned delays in retrieving the URL
Thanks in advance!
You can convert your three lines of code to single line by replacing these,
// Points to the root reference
FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:#"gs://project-3229518851181635221.appspot.com"];
// Points to "images"
FIRStorageReference *imagesRef = [storageRef child:#"articleImages"];
// Note that you can use variables to create child values
FIRStorageReference *imagePath = [imagesRef child:articleObj[#"image"]];
to
// Points to the root reference
FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:#"gs://project-3229518851181635221.appspot.com/articleImages/image"]];
Base on your requirement there is one extended class for SDWebImage which just takes reference of FIRStorageReference and downloads your image.
See below code for same:
FIRStorageReference *storageRef = [[FIRStorage storage]
referenceWithPath:[NSString stringWithFormat:#"/folder/images/%#",aDictCardData[#"url"]]];
[cell.imgDetail sd_setImageWithStorageReference:storageRef
placeholderImage:nil
completion:^(UIImage *image,
NSError *error,
SDImageCacheType
cacheType,
FIRStorageReference *ref) {
if (error != nil) {
NSLog(#"Error loading image: %#", error.localizedDescription);
}
}];
In above code you can find sd_setImageWithStorageReference method with storageRef to easily access FireBase Image.
You can find this Extended Class file from Here:
https://github.com/firebase/FirebaseUI-iOS/tree/master/FirebaseStorageUI
Add These classes to your project and use this method.
Hope this will helps you to get FireBase Image without getting NSURL of that image.
Related
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;
}
Just to better give an idea of what this post is about, it ultimately ends out in this question:
How do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?
This is the backbone question of this post, however for good measures and to actually allow one to understand what I mean, I have added my specific case involving this question in the following post. I know it's long, but it's pretty complex to explain my case.
So, here goes nothing:
I have a tableView which loads a couple of things from different arrays. All the arrays is created from a JSON fetch each time the app is launched, the idea being that I can update the info in my app by simply updating the JSON text file. One of the entries in the JSON text file contain url's to images, which I want to add to the tableView cell's contentview. I got the JSON fetch working, and a dictionary "dataDictionary" containing the info from the fetch is created.
The arrays then, get created like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Array is created from the dictionary created from the JSON fetch
_dataArray = _dataDictionary[#"games"];
// Arrays that will eventually hold info from JSON file is created
_Titles = [[NSMutableArray alloc] init];
_Descriptions = [[NSMutableArray alloc] init];
_Date = [[NSMutableArray alloc] init];
//images will be added to dictionary instead
_imageDict = [[NSMutableDictionary alloc]init];
// Info parsed from the JSON fetch, is now added to arrays
for (NSDictionary *eachData in _dataArray)
{
_Title = eachData[#"Title"];
_Description = eachData[#"Description"];
_Date = eachData[#"Release"];
// Key for image url is created
_image = eachData[#"ImageLink"];
[_Titles addObject:_Title];
[_Descriptions addObject:_Description];
[_Dates addObject:_Date];
Now there is more code below this, which is where I handle the images (will come just after this short explanation and below code sample), as you can see I have specified a key named "image" for the ImageLink entry in the JSON. Now I call this method, which starts an asynchronous download of the images:
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^) (BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
This method is called in the below code, so to continue the code from before:
// Just to be clear, we are now back in the "for (NSDictionary *eachData in _dataArray)" statement from the first code sample
//calling the above method here
[self downloadImageWithURL:[NSURL URLWithString:_image] completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
//All this code runs when an image is successfully downloaded
NSLog(#"image download succesfull");
UIImage *downloadedImage = [[UIImage alloc] init];
// Doing this so I can resize the downloaded image to a proper size
downloadedImage = image;
//long boring code to resize image here
UIImage *resizedImage = [[UIImage alloc] init];
// resizedImage is as the name implies, the resized image that now has a proper size for the cell's content view
_number++; //Int that start out as -1, this increments each time an image is downloaded to allow the images to be added to the dictionary with the keys 0, 1, 2...
// Wrapping the int in a NSNumber
NSNumber *number = [NSNumber numberWithInt:_number];
// Turning the NSnumber into a string
NSString *key = [number stringValue];
// Images finally being added to the dictionary, but, in the wrong "order" - or rather, with the wrong keys / numbers
[_imageDict setObject:resizedImage forKey:key];
//Update tableView when images are added to dictionary
if (_imageDict.count == _gameTitles.count) {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}
}
}];
//Update tableView when data is added to array, this is to allow info to be shown even before the images are done downloading
if (Titles.count == _dataArray.count) {
// Update tableView with loaded content
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}
} // "for" statement ends here
}
To sum the most important parts up:
When filling the cell's in the tableView, I use the indexPath.row to get the proper info from each array.
The images download, and I add them to an dictionary with the keys 0, 1, 2.. when finished, however, because the download is asynchronous the download do not finish in the order it was initialized, rather, the smaller images get (not surprisingly) downloaded and added to the image dictionary first. This means that the keys of the images does not fit with the order in all my other arrays, so setting the image in the first cell with indexPath.row simply sets the image that was downloaded first, rather than the image download that was started first. Basically, even if image 5 gets downloaded first, it should be added with the key "4", and not "0".
I should also mention that because of the JSON fetch I do NOT know how many pictures will be downloaded beforehand as this can change depending on the JSON. This cancels out a lot of the answers here on stackoverflow (and other places as well).
So all this text and code and whatnot leads to the beginning question, how do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?
Thank you very much for taking your time to read this.
So all this text and code and whatnot leads to the beginning question, how do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?
You can use SDWebImage Library to download images,it asynchronously downloads your images from given URLs and it also caches images and manages all the things for you and it is really popular.All you have to do is to add below code to your -cellForRowAtIndexPath delegate method:
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:imageURLThatYouGetFromJSONResponse]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Also I recommend you not to use arrays or dictionaries.You can store that all information in an object.It is easy and best-practise in many cases and called as object-oriented approach.You create an NSObject subclass and in there create properties "title", "description", "name", "imageURL".You can follow this OOP Tutorial for some better understanding.
You don't care about number of images, because you define the number of rows in -numberOfRowsInSection and -cellForRowAtIndexPath is called the times you wrote in -numberOfRowsInSection.You can add:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [arrayOfYourObjects count];
}
Edit: How to manipulate images while using SDWebImage?
Answer: You can use SDWebImageManager, it also manages caching for you
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// Code to resize
//After resizing
cell.imageView.image = resizedImage;
}
}];
I want to save an image (which is downloaded from server) in iOS device photo album and store that image photo album url in local database. My question is, How do i get that photo album image url after saving the image?
I am able to save the image in photo album using the following ALAsset code: But, I need this url image also to be stored in my local db. So next time, i won't download the same image from server and i can load directly from device photo album.
[self.maAssetsLibrary saveImage:image
toAlbum:#"My-Album"
completion:completion
failure:nil];
Please suggest.
UPDATE:
I tried this, but NOT getting me the photo album image URL after saving the image.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// The completion block to be executed after image taking action process done
void (^completion)(NSURL *, NSError *) = ^(NSURL *assetURL, NSError *error) {
};
[self.maAssetsLibrary saveImage:image toAlbum:#"My-Album" completion:^(NSURL *assetURL, NSError *error){
if (error) {
NSLog(#"error");
} else {
NSLog(#"url %#", assetURL);
}
} failure:^(NSError *error) {
}];
});
Due to sandboxing you can't get such a url. As #Lord Zsolt proposes in the comment, you can overcome this by saving images in your application's folder. In this case you might e.g. give them a name that serves as key to identify each image.
EDIT
As #MidhunMP commented, I was wrong on that! There is a way (and I'm happy to know that now) and it comes from this Stack Overflow answer, provided by #CRDave in the comment above.
The main point is to use ALAssetsLibrary's writeImageToSavedPhotosAlbum:orientation:completionBlock: method.
It's always nice to learn.
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....!
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.