How to cache CollectionViewCell images that is part of custom UITableViewCell - ios

So, I have this news feed structure. Every news block is a custom UITableViewCell. Now, every custom cell has a CollectionView that shows images.
The problem is that when scrolling, and news cell (news block) comes out visible, the CollectionView is reloading - every time cell shows up. I'm trying to find the way to cache those images on the main ViewController side.
What would be the best approach?

If you want to avoid implementing your own cache/disk/memory handlers for this kind of job I strongly recommend either AFNetworking or SDWebImage that handle it all for you.
An example of SDWebImage on how to set an image and its cache:
[imageView sd_setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
This handles all the cache/disk/memory for you automatically.
Here is example of how you handle cache with SDWebImage:
// Set memory size limit (check with older devices to avoid memory errors when setting it to high value.)
[[SDImageCache sharedImageCache] setMaxMemoryCost:****];
// Clear disk cache
[[SDImageCache sharedImageCache] clearDiskOnCompletion:^{
}];
// Clear memory cache
[[SDImageCache sharedImageCache] clearMemory];
I would stay away from NSUserDefaults or the above mentioned example setting the images to dictionary or arrays to avoid memory errors and app crashes/performance.

You have to cache your UIImage instance, so, if you get some pictures from any source, you might write something like below:
/* some code before */
UIImage *yourImage = /* Get your UIImage from any available way */
[self.cache setObject:yourImage forKey:#"SomeUniqueID"];
/* some code after */
I guess, this code will look greater as a function, which obtains UIImage instance and it's id as parameters.
For my opinion, it's the most common way to store images. If you need a more proficient way without any overheads with third-party frameworks, you can read the documentation about NSCache.
But in general, you have a lot of different ways how to store your images:
Get the image from the disk
Get the image from the memory
Use one of the variants above, but apart them still use some lightweight & flexible frameworks, you can find a lot of on the GitHub.
So, the code I wrote above will just store images into the memory cache.

Related

How to put my url images into carousel?

I am getting URL images from API. I have stored the UR images into string. Here my problem I want to display that into carousel. I don't know how to do this. Below is my code that I have tried.
for(int i=0;i<hotelIMAges.count;i++ )
{
NSDictionary *images=[hotelIMAges objectAtIndex:i];
NSString *Images= [images objectForKey:#"Imagepath"];
[ImageARRAy addObject:Images];
} //Here I have stored all the images into string..(https://***.jpg)
Carousel *carousel = [[Carousel alloc] initWithFrame:CGRectMake(0, 0,300, 262)];
[carousel setImages:ImagesARRAy];
You just need to process asynchronously as your current approach can be a synchronous request and blocks the main thread until that download is complete.
Consider using SDWebImage and the UIImageView extension it adds to manage the image download and display. It also supports a placeholder image that you can supply to indicate that the download is in progress.
If your using any third party carousel check for asynchronous methods to display the images.I believe setImages: method must be for images that are stored locally inside app. Do let me know if you have any queries.
----EDITED----
Your Carousel is using synchronous method to display which blocks the main thread.
You can use SDWebImage for your purpose.
For this you need to download SDWebImage for asynchronous downloading of image. You can download SDWebImage from link below.
You can use it in your Carousel.h class as
#import <SDWebImage/UIImageView+WebCache.h>
modify your setup method with below line.
[imageView sd_setImageWithURL:[NSURL URLWithString:self.images[i]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
For details refer github page of SDwebpage. DO let me know if you have any queries.
https://github.com/rs/SDWebImage#using-asynchronous-image-downloader-independently

SDWebImage not updating image in cache

I am using SDWebImage library for handling image cache. I implemented one of its method to download image from server and populating data in tableView. Here is my code for downloading image and showing in tableViewCell
In cellForRowAtIndexPath, I did the following code
[cell.profileImageView sd_setImageWithURL:[NSURL URLWithString:url] placeholderImage:nil options:SDWebImageRefreshCached];
It works perfectly here. But when I updated image on server, the tableViewCell still shows the same image. The problem is ,its cache is not updating image. I searched with the same keywords and came across this question on StackOverflow. But couldn't resolve the issue. I also tried to clear memory and cache on viewDidDisappear
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:YES];
[[SDImageCache sharedImageCache]clearMemory];
[[SDImageCache sharedImageCache]clearDisk];
}
But its not efficient way. Is there any other way to update the cache when image on server is updated ?
You can use SDWebImageRefreshCached option to refresh concrete image, however you never know if the image was changed on the server side. So it's only your decision should you use image cache or not and when your cache should be updated.
From SDWebImage docs :
If you don't control the image server you're using, you may not be able to change the URL when its content is updated. In such case, you may use the SDWebImageRefreshCached flag. This will slightly degrade the performance but will respect the HTTP caching control headers
[imageView sd_setImageWithURL:url
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
options:SDWebImageRefreshCached];
It would defeat the purpose of Image Caching if you use this in all your code. Coz the image will be downloaded everytime. Instead you could :
If you manage the server, you could setup some flags in the DB to indicate the image has changed on the server.
Implement some logic to only use SDWebImageRefreshCached once in 2 days or something like that, and use the normal code all other times.
You could also clear the entire cache once in a while to force a refresh, if that's feasible.

How to get the image from in memory cache using SDWebImage?

I have implemented SDWebImage in cellforrowatindexpath method. The problem here is when i scroll down the tableview the images are loading from web which is expected but when i scroll up the tableview the images are again downloading. What is the way to get rid of this problem. I don't want to load images from url overtime (since there is a usage limit with the web service call). I chose to use SDWebImage API for in memory cache so that once the images are loaded they are taken from cache to show it on the UI instead of making the web service call everytime. Here is my code:
[cell.placeImage sd_setImageWithURL:url placeholderImage:nil options:SDWebImageCacheMemoryOnly];
The above line is the only code in that method. Please help me.
Any kind of help is appreciated. I have seen other questions in this community but nobody said how to get an image from the cache memory.
For refreshing of the images you should use option : SDWebImageRefreshCached
[imageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRefreshCached];

How to load large amount of images in iOS?

I am developing an app which displays large number of images. when i'm loading images there's is no problem on loading of 1000 images when the image crosses the 1000 the app crashes and memory issues raised. How to solve it?
Is there any way. Kindly help me?
Thanks in Advance
Your app is crashing due to consuming too much memory. So you need a strategy to manage the memory your app uses.
You need to set up a pooling-type system where you only load those images that might be displayed "soon".
For example, if the user is looking at image 500, you could have 498, 499, 501, and 502 loaded into memory.
If they move to image 501, then you deallocate 498, and load 503.
What constitutes "soon" is dependent upon how your app's flow works, but you should be able to come up with something workable.
You need to come up with a tableview-like system.
If you look at them, all cells aren't loaded at the same time ; you can experience this if you load your app with a tableview, then put a breakpoint in cellforrow, and you'll see it being called when you start scrolling.
Because all cells aren't loaded at once.
You need a similar logic with your images ; you should NEVER load all of them at once. And if you have a reason to, then I strongly suggest loading a thumbnail version of them ( a really low quality version of them, for a grid-like view of all your pictures, for example ).
To achieve that you need to know what pictures need to be immidatly visible, and what pictures are likely to be visible soon. For small numbers let's say you have 10 pictures total (instead of 1000).
User is loading the app, and can see picture 1 and 2 on the screen. You'll obviously load them straight away, as well as picture 3 and most probably picture 4 (just to make sure). When he scrolls, you KNOW that he is scrolling and your need a method that reads that scroll to allocated the right pictures dynamically. You'll deallocate picture 1 first (because its the first to go away ) and start loading picture 5.
That way, instead of having 1000 pictures loaded, you have only up to 5 (depending on the size obviously).
A really good example is the 9gag app, which loads pictures right under the screen, which means by the time you read the first gag, the next one is already loaded. The "only" way to see it load is to scroll a bit faster or to have a poor connection.
This logic is, to me, the most reliable and effective way to load a big amount of data, wether it is pictures or anything else.
Using stuff like cache, arrays, scrollview(s) and maybe files (depending on your app really) is a good starting point.
Probably you're getting the image-date from some API, because it's ridiculous to save it into device.. :)
My solution is to work with UITableView or UICollectionView depends on what design flow you're are choosing, because those UIComponents will manage memory for you a.k.a will allocate the visible once + 5 down and + 5 up, and deallocate the others.
Also you will need to download the image-date asynchronically, so you
won't block the main UI - thread.
Now... if i were you, I'll definitely go for third party library such as AFNetworking in your case, its best for handling JSON data from server, also have very good error handling.
Here I'll post a method witch is for downloading image-data from server.
- (void)downloadImageAsynchronicallyByUrlString:(NSString *)url forIndexPath:(NSIndexPath *)indexPath {
// Perofrm request
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc]initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
[requestOperation setResponseSerializer:[AFImageResponseSerializer serializer]];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// IF SUCCSESS send dataSource SUCCSESS message
[dataSource downloadFinished:responseObject forIndexPath:indexPath];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// IF FAIL send dataSource FAIL message
[dataSource downloadFailAtIndexPath:indexPath];
}];
// Start request!!!
[requestOperation start];
}
Explanation:
You call every time this method in cellForRowAtIndexPath at your tableView or itemForRowAtIndexPath if you use collectionView and I'm using a protocol to handle when responseObject a.k.a theImage to send the responseObject via protocol method.
In your UIClass where your tableView pr collectionView lives just implement your protocol method
- (void)downloadFinished:(UIImage *)image forIndexPath:(NSIndexPath *)indexPath {
// Creating copy of cell
_lecturerCell = (LecturersCell *)[_lecturersTableView cellForRowAtIndexPath:indexPath];
// Set downloaded image
[_lecturerCell setLecturerImage:image];
}

UICollectionView received memory warning while scrolling

I am developing an App with an UICollectionView in one ViewController.
This CollectionView it's a gallery with 3 images in each row.
I get the images from the server in groups of 30 pictures, first url and code, and when the cell is going to be displayed in cellForItemAtIndexPath I use SDWebImage Library to download those pictures asynchronously. Every 30 pictures I call again the web service and I get 30 pictures more sending the request with limit and offset. This could happen 100 times, there are profiles with 3000 pictures.
Well, my problem comes one I launch the app in my iPhone 4, after some fast scrolling I start getting Received Memory Warnings and after some warnings the app crashes. When I make the same test in the Simulator, nothing bad happens.
Each time I download 30 pictures I add the result array to the NSMutableArray *data property which handles the CollectionView data and reload the collectionView. I have tried to use Instruments with allocations, but it is very difficult for me to understand what is happening.
This is the code I use to create the cells
-(UICollectionViewCell *)collectionView:(LZProfileImagesCollectionView *)aCollectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if(aCollectionView == self.imagesCollectionView){
if([self.data count] - 12 == indexPath.row){
self.photoOffset += 30;
[self loadUserData];
}
LZProfileCollectionViewCell * cell = (LZProfileCollectionViewCell *)[imagesCollectionView dequeueReusableCellWithReuseIdentifier:ImageCollectionCellIdentifier forIndexPath:indexPath];
Image *image =(self.data)[indexPath.row];
[cell configureCellWithImage:image];
return cell;
}
return nil;
}
And in the LZProfileCollectionViewCell cell I have this one:
-(void)configureCellWithImage:(Image *)image
{
[self setNeedsDisplay];
// Imagen
NSString *imageUrl = [kBaseURL stringByAppendingString:image.imageStringUrl];
[self.pictureImg setImageWithURL:[NSURL URLWithString:imageUrl] placeholderImage:[UIImage imageNamed:kDefaultLoadingImage]];
[self.pictureImg setContentMode:UIViewContentModeScaleAspectFit];
}
I have taken setImageWithURL from SDWebImage
After 3 minutes I get this snapshot in the Instruments(Statistics)
Thank you in advance
Well, you are caching every image in an array, so once you cache so many images you are eventually going to run out of memory. It doesn't happen on the simulator because the simulator has as much memory as the computer running it, so it is unlikely to ever run out of memory, a real device however will run out very fast. I'd handle the memory error yourself and empty the array, to free up new space for new images. Or better yet create your own caching mechanism to hold and clear images that haven't been in view for a while, either way you have to have some way to dump images after you hit a certain threshold.
In the method – didReceiveMemoryWarning you should free some memory of the array where you are stored the images. Maybe the first images that you are not displaying stored it a second array like an auxiliar array.
This is probably happening because a lot of UIImage objects end up in memory. You could instead cache this on disk. AFNetworking has a nice UIImageView category that does all of this automatically.
If you're not saving the Images in your dataSource array, then they're probably being retained somewhere else, which is a bug.
Also, make sure you're dequeuing cells and not creating new ones.
I've experienced similar situation so I'm replying on this issue though the reason seems a little different from yours.
I have a horizontal UICollectionView inside UITableViewCell. When I scroll UICollectionView right and left rapidly, the entire UI(even a home button) stops and after a while the app crashes with the message "Message from debugger: Terminated due to memory issue" on the console even without didReceiveMemoryWarning call.
I tested it with a UICollectionViewCell without any UI components but no changes. XCode Profile tool says it's actually keep allocating memory during the UI's stopped until it crashes even without any UI components in it.
The only suspicious point was that I was setting the width of a UICollectionViewCell's subview(custom UIView pinned to all superview edges) with AppDelegate.window.size.width * 0.35 to determine UICollectionViewCell's width and it was a float value like 130.25
When I hardcode this value to 130, all the crashes are gone. I think something was happening under the hood with this vague value of 130.25 and it's determining the width as 130 or 130.25 occasionally.
So I'm using CGFloat(Int(AppDelegate.window.size.width * 0.35)) now and everything's fine.

Resources