Want to achieve image gallery through UICollectionView. I have a set of image url's in an array, say around 20-30 url's. Using SDWebImageManager to download images and cache it and display on the collection view.
See my code below:
for(int i=0;i<[imagePath count];i++) {
[manager downloadWithURL:[NSURL URLWithString:[imagePath objectAtIndex:i]] options:0 progress:^(NSUInteger receivedSize, long long expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if(image){
NSLog(#"SUCCESS");
NSString *localKey = [NSString stringWithFormat:#"Item-%d", i];
NSLog(#"%#",localKey);
[[SDImageCache sharedImageCache] storeImage:image forKey:localKey];
}
}];
}
All it does is, display the first image that has the url link indexed 0 in my imagePath array and the rest of the cell's remain blank. When tried to print it just displays SUCCESS once and Item-0. I guess its not going any further. It downloads just one image(first url's image in the array). Please help me with this. I am breaking my head on this from a long time. Not sure if i am on right track. Or please do suggest me other alternatives of achieving image gallery through multiple url's stored in an array.
I have to wonder what your cellForItemAtIndexPath is doing. If it's trying to retrieve the images from the cache directly, you might see precisely the behavior you describe (where the above code is running asynchronously and thus the images are not yet downloaded by the time cellForItemAtIndexPath tries to retrieve them from the cache).
Personally, I'd suggest that you do not even bother trying to populate your cache in advance. Just have cellForItemAtIndexPath just use the UIImageView+WebCache category method setImageWithURL and remove the for loop in your question altogether. It will automatically fill the cache for you and the problem of missing images will probably go away.
If you're having troubles retrieving your images, you should use one of the SDWebImage methods that passes a NSError object back to the block. For example:
[cell.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
if (error)
NSLog(#"%s: setImageWithURL error: %#", __FUNCTION__, error);
}];
If the URL address is not valid, you may see a 404 error.
If you're determined to use your for loop, a couple of other thoughts:
BTW, you are logging only if image is non-nil. You might want an else statement for your if statement fails (e.g. perhaps the error object can tell you something interesting). If an API provides an error object, you should avail yourself of it.
It's a little curious that are taking a downloaded image and adding it to the cache with another key; now you have two copies of the image in your cache, which is a wasteful use of space. Once you've downloaded an image, it's already in the cache, and if you try to retrieve the image again using the same URL, it will pull it from the cache. There's no point in creating another entry in the cache with your own key.
If you're not going to use the progress block, you can just pass nil for that parameter.
Related
I am using the UIImageView+AFNetworkingin order to download and display an image from a URL in a UITableView. Everything is working great and my question is around the caching elements & allocations that are implemented with this call.
In using instruments to track memory I see that as I scroll my largest memory allocation quickly becomes this VM: Foundation which appears as if it is caching the images I am downloading in some way.
Which is great for user when the view the same image but I never see it release the memory. (I have not been able to get it to a memory warning yet). I just want to make sure then when needed this allocation will get released when needed. Below is the stack track of those VM : Foundation
Is there any need for me to monitor this and release the memory for the cache when needed to ensure smooth scrolling? Or is there anything else I should be watching to ensure this is handled properly?
Here is how I am calling for the images in cellForRowAtIndexPath I am calling the setImageWithURLRequest. Any advice or improvements would be appreciated.
NSString *imageURL = [NSString stringWithFormat:#"https://IMAGE-URL/%#",imageURL];
__weak MainTableViewCell *weakCell = cell;
NSURLRequest *urlRequest = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:imageURL]];
[cell.postImage setImageWithURLRequest:urlRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
MainTableViewCell *strongCell = weakCell;
strongCell.postImage.image = image;
[UIView animateWithDuration:.15 animations:^{
strongCell.postImage.alpha = 1;
}];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
}];
You really shouldn't worry about clearing image cache as AFNetworking handle this for you automatically.
It stores images internally in NSCache that as the docs say :
incorporates various auto-removal policies, which ensure that it does not use too much of the system’s memory. The system automatically carries out these policies if memory is needed by other applications. When invoked, these policies remove some items from the cache, minimizing its memory footprint.
However, during AFNetworking development there were some troubles with this automatic removal behaviour, so finally manual cache clearing was added when receiving UIApplicationDidReceiveMemoryWarningNotification. But this all happens internally and you shouldn't do it yourself.
There is a image URL http://example.com/xxoo.jpg
Open this URL from Chrome, save image to desktop to check the image file size.
Open this URL from iPhone safari, save image to camera roll, than send it to computer to check the image file size.
Save the image in my iOS app by using UIImageWriteToSavedPhotosAlbum, and than send it to computer to check the image file size.
Strangely, these files are all different from each other. The difference can be hundreds of kb.
I had tried download image by using AFNetworking setImageWithURL, SDImageView setImageWithURL, SDImageDownloader downloadImageWithURL and dataWithContentsOfURL. These downloads are the same size but different from the size on the server.
There is an example code:
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL
options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
_imageToBeDownload = image;
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}];
These are the possible reasons:
The camera roll may use a different file format than the original file format on the server.
SDWebImageDownloaderreturns an image not a NSDataobject containing the JPEG file from the server. When saving an image with UIImageWriteToSavedPhotosAlbum the file format can be different than that on the server.
Proxies in the Internet per default are allowed to convert the media types (in particular images) to a different one, possibly with a higher compression level in order to save space in the cache. So, a GET request may return a cached version of the original file on the server which has lower resolution. (Reference: 14.9.5 No-Transform Directive, RFC 2616)
So here's the deal. I recently started using AFNetworking to download a few files on start using the following code:
NSMutableURLRequest* rq = [api requestWithMethod:#"GET" path:#"YOUR/URL/TO/FILE" parameters:nil];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:rq] autorelease];
NSString* path=[#"/PATH/TO/APP" stringByAppendingPathComponent: imageNameToDisk];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"SUCCCESSFULL IMG RETRIEVE to %#!",path)
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Deal with failure
}];
With my path actually plugged into the path variable (Sorry, not on the right computer right now to actually copy pasta the text, but it's exactly the same as the above with different path stuffs)
And everything is working great! I'm getting the file successfully downloaded and everything. My current issue is that I'm trying to get caching to work, but I'm having a lot of difficulties. Basically, I'm not sure what I actually have to do client side as of AFNetworking 2.0. Do I still need to set up the NSURlCache? Do I need to set the caching type header on the request operation differently? I thought that maybe it was just entirely built in, but I'm receiving a status of 200 every time the code runs, even with no changes in the file. If I do have to use the NSUrlCache, do I have to manually save the e-tag on the success blocks requestoperation myself and then feed that back in? Any help on how to progress would be much appreciated. Thanks guys!
AFNetworking uses NSURLCache for caching by default. From the FAQ:
AFNetworking takes advantage of the caching functionality already provided by NSURLCache and any of its subclasses. So long as your NSURLRequest objects have the correct cache policy, and your server response contains a valid Cache-Control header, responses will be automatically cached for subsequent requests.
Note that this mechanism caches NSData, so every time you retrieve from this cache you need to perform a somewhat expensive NSData-to-UIImage operation. This is not performant enough for rapid display, for example if you're showing images in a UITableView or UICollectionView.
If this is the case, look at UIImageView+AFNetworking, which adds downloads and caching of UIImage objects to UIImageView. For some applications you can just use the out-of-the-box implementation, but it is very basic. You may want to look at the source code for this class (it's not very long) and use it as a starting point for your own caching mechanism.
I am making an app..
I need to download many pictures from a server, but I don't know how to do :(
I try to use this code:
UIImageView *image=(UIImageView *)[cell viewWithTag:100];
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:#"FileName"]];
if ( data == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
image.image=[UIImage imageWithData:data];
});
data=nil;
});
This code is slow... I need to do it more quickly... I think AFNetworking is the best option, isn't it ?
You have several options, one of the AFNetworking. However, if you go with AFNetworking, you should use the UIImageView+AFNetworking class, inside the UIKit+AFNetworking folder.
Here's the documentation for it: http://cocoadocs.org/docsets/AFNetworking/2.0.1/Categories/UIImageView+AFNetworking.html
Another great option is SDWebImage which gives you more advanced control over caching and image handling, such as processing images before they're displayed, handling your own caching, etc.
AFNetworking would be perfect for your needs. It has its own UIImageView category that handles asynchronous image loading very smoothly. Give it a try.
Like previous answers have suggested, AFNetworking is a great way to go.
I too, though, want to suggest using SDWebImage. There are a lot of nice features in there, such as cool cache handling and image decompression.
For starters, try doing something like
[self.image setImageWithURL:[NSURL URLWithString:imagePath]
placeholderImage:[UIImage imageNamed:#"placeholder"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
[image setImage:image];
}];
When it comes to speeding up the code, I am not sure why your code is slow in the first place since you are already doing it asynchronously.
AFNetworking is a fantastic framework and will make easy for you to do async image request.
However using AFNetworking will not speed up your code.
Probably will make your code slow, but the difference should be irrelevant, only if your images are too larger and the time to decode the image are high (I don't think this is the problem).
I am trying to build a nice function to access the network for images, if they are found on the web, I store them in a cache system I made.
If the image was already stored on the cache, I return it.
The function is called getImageFromCache and returns an image if it is in the cache, else, it would go to the network and fetch.
The code might look like this:
UIImageView* backgroundTiles = [[UIImageView alloc] initWithImage[self getImageFromCache:#"http://www.example.com/1.jpg"]];
Now, I am moving on to using threads because of big latencies due to network traffic. So I want images to show a temp image before I get the result from the web.
What I want to know is how can I keep track of so many images being accessed sequentially, being added to UIImageViews by this function (getImageFromCache).
Something just won't work there:
-(UIImage*)getImageFromCache:(NSString*)forURL{
__block NSError* error = nil;
__block NSData *imageData;
__block UIImage* tmpImage;
if(forURL==nil) return nil;
if(![self.imagesCache objectForKey:forURL])
{
// Setting a temporary image until we start getting results
tmpImage = [UIImage imageNamed:#"noimage.png"];
NSURL *imageURL = [NSURL URLWithString:forURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
imageData = [NSData dataWithContentsOfURL:imageURL options:NSDataReadingUncached error:&error];
if(imageData)
{
NSLog(#"Thread fetching image URL:%#",imageURL);
dispatch_async(dispatch_get_main_queue(), ^{
tmpImage = [UIImage imageWithData:imageData];
if(tmpImage)
{
[imagesCache setObject:tmpImage forKey:forURL];
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
return [imagesCache objectForKey:forURL];
return tmpImage;
}
This is not a direct answer to your question, but are you aware that there is no need to use GCD to download things asynchronously (on a background thread)? Just use NSURLConnection and its delegate methods. All your code will be on the main thread but the actual connection and downloading will happen in the background.
(And in fact I have written a class, MyDownloader, that takes care of all this for you:
http://www.apeth.com/iOSBook/ch37.html#_http_requests
Scroll down to the part about MyDownloader and its subclass MyImageDownloader, which is doing exactly the sort of thing you need done here. Moreover, note the subsequent code in that chapter showing how to use a notification when a download completes, prompting the table view that need these images to reload the row that contains the image view whose image has just arrived.)
its good your building it from scratch but if you want to save the all the work, there's a drop in Replacement SDWebImage Library with support for remote images coming from the web, and has all the functionality Like Temp Image, Asychronous Loading, Caching etc, you said you need
In your background thread, once the download has completed and you've saved the image to the cache, I'd suggest you post a notification using the NSNotificationCenter to let other parts of your app know that the cache has been updated.
This assumes that whichever part of the app manages the image views has registered its interest in those notification with the addObserverForName method. When it receives such a notification, it can then attempt to retrieve the images from the cache again and update its image views if appropriate.
Depending on the number of image views, you may want to pass through the image url in the notification in some way (e.g. in the userInfo dictionary), and then based on that decide which image views should be refreshed rather than refreshing them all.
I should add that I would also recommend getting rid of the inner dispatch_async call. There's no need for that, although you may need to add synchronisation to your cache object so it can be safely accessed from the main thread as well as the download thread.