I have table with Images cell. Images download from internet and save in local disk. Count = 200. Tableview show this images. When scroll content to bottom, comes message memory warning... Used memory 250 - 300 mb O_O!!! Links to images that do not keep.
NSString *cellID = #"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
...
NSString* imagePath = [arrayContent objectAtIndex:indexPath.row];
UIImage* image = [[UIImage alloc] initWithContentsOfFile:imagePath];
[cell.imageView setImage:image];
Why hide images not release?
Replace this line
UIImage* image = [[UIImage alloc] initWithContentsOfFile:imagePath];
with this and check once
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
Well I used a custom MSAsyncImageCell class that replaces its image when reused by iOS. It loads the image from a URL asynchronously, displaying a "loading" image gracefully and release memory when reused for another image. If you want I can post the code here but the code is a bit long.
Here is the part of code that actually loads the image. I have images off web/caches so it is NSData.
- (BOOL)_loadImageWithData:(NSData *)imageData
{
UIImage *image = [UIImage imageWithData:imageData];
// Just in case loading failed.
if (image)
{
// Extra check - don't mix up.
if ([[self.currentURL absoluteString] isEqualToString:[self.imageURL absoluteString]])
self.asyncImageView.image = image;
return YES;
}
return NO;
}
Related
I have created collection view programmatically where i have created UIImageview programmatically within collectionViewCell and images are displayed in imageview are downloaded from server asynchronously.
Code below is written in cellForItemAtIndexPath: method -
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSURL *imageURL = [NSURL URLWithString:[arrmImgPaths objectAtIndex:indexPath.row]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
// [imgvMainView removeFromSuperview];
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell addSubview:imgvMainView];
[imgvMainView setImage:image];
imgvMainView.contentMode = UIViewContentModeScaleAspectFit;
});
});
Problem is, when i scroll collection view some images are overlapped on one another. Please tell me solution to avoid it.
Thanks in advance.
Please use below line I hope this would work what I am assuming problem of duplicate cell.
UIImageView* imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell.contentView addSubview:imgvMainView];
This is most likely happening because you are reusing cells (correctly) and that the previous use of the cell is still calling the previous image. You must cancel the async task when the cell goes out of view. By the way, it will make you life much easier to use AFNetworking+UIImageView to do this.
One other option is to check that there is no UIimageView in your cell before you create one.
dispatch_sync(dispatch_get_main_queue(), ^{
UIView *viewToRemove;
for (UIView *view in cell.subViews) {
if (view isKingOfClass:UIImageView){
viewToRemove = view;
}
}
[viewToRemove removeFromSuperView];
imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell addSubview:imgvMainView];
[imgvMainView setImage:image];
imgvMainView.contentMode = UIViewContentModeScaleAspectFit;
});
I created a custom class called Slot and it is a subclass of UIView. In the slot class, the following function is called:
- (NSString*)assignItem:(Item*)item {
NSString *message;
if (item.slotSize + currentCapacity > slotSize) {
message = #"Sorry this item will not fit.";
} else {
//uiimageview stuff
UIImage *image = [[UIImage alloc] initWithContentsOfFile:item.imageName];
UIImageView *iv = [[UIImageView alloc] initWithImage:image];
[self addSubview:iv];
[items addObject:item];
currentCapacity = currentCapacity + item.slotSize;
message = #"Success";
}
NSLog(#"%#",message);
return message;
}
What happens is an Item (another custom class, subclass of NSObject) is passed to the Slot a UIImageView is created from the item from a string to an image in the bundle and it is added to the subview. However, the image isn't showing. The Success message is showing so I know its getting in there. Is there another way to add a subview from the subclass or am I just doing it all wrong?
UIImage *image = [[UIImage alloc] initWithContentsOfFile:item.imageName];
This is not the correct way to instantiate an image that you have in the bundle. You can either use imageNamed: like this,
UIImage *image = [UIImage imageNamed:item.imageName];
Or, you can get the image with initWithContentsOfFile like this,
NSString *imagePath = [[NSBundle mainBundle] pathForResource:item.imageName ofType:#"JPG"]; // replace JPG with whatever is appropriate for your image.
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
imageNamed: will cache the images, the other way doesn't.
In my application I have used custom tableviewcell for loading two images in one cell, my problem is when I am loading that images on my view first time, it show swaped images (1st image in 2nd image and 2nd in 1st) and then after fraction of seconds it loads proper images (1st in 1st and 2nd in 2nd image). I am using AsyncImageView Class for display image.
Every first time view load it shows wrong and then after correct every time.
In CellForRow
AsyncImageView *imageView = [[AsyncImageView alloc] initWithFrame:celllookAlik.btnFullImg.frame];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.clipsToBounds = YES;
imageView.tag = IMAGE_VIEW_TAG1;
[celllookAlik addSubview:imageView];
AsyncImageView *imageViewll = [[AsyncImageView alloc] initWithFrame:celllookAlik.lookAlikeImage.frame];
imageViewll.contentMode = UIViewContentModeScaleAspectFill;
imageViewll.clipsToBounds = YES;
imageViewll.tag = IMAGE_VIEW_TAG2;
[celllookAlik addSubview:imageViewll];
Display Image in cell using below code:
NSString * imageImage = #"";
imageImage = [[self.arrayResponseLookAlike objectAtIndex:indexPath.row] objectForKey:#"ImagePath"];
AsyncImageView *imageViewCelebrity = (AsyncImageView *)[celllookAlik viewWithTag:IMAGE_VIEW_TAG1];
//cancel loading previous image for cell
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:imageViewCelebrity];
//load the image
imageViewCelebrity.imageURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#",imageImage]];
NSString * imageImageceleb = #"";
imageImageceleb = [[self.arrayResponseLookAlike objectAtIndex:indexPath.row ] objectForKey:#"LookLikeImage"];
AsyncImageView *imageViewllTemp = (AsyncImageView *)[celllookAlik viewWithTag:IMAGE_VIEW_TAG2];
//cancel loading previous image for cell
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:imageViewllTemp];
//load the image
imageViewll.imageURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#",imageImageceleb]];
I have suffered with this type of problem, i have used SDWebImage.frameword.
This framework integration is very easy, and it will load the images properly.
For this first we need to create UIImageView in place of AsyncImageView.
Display Image in cell using below code:
[mMainImgView setImageWithURL:[NSURL URLWithString:loImgurl] placeholderImage:[UIImage imageNamed:#"placeholder"]];
for remaining details check with https://github.com/rs/SDWebImage
I have a image url plist with 50 image urls.Iam trying to display that in a UICollectionview but it getting too slow.Iam displaying 9 cells(images) at a time like 3x3 (3 rows and 3 columns).How can i display first 9 images at loading time and next 9 images on next scrolling .?is that possible? i tried SDWebImages for loading images ,Its not working for me.Please help me.
CODE INSIDE collectionView cellForItemAtIndexPath: ()
the code inside comment take lots of time for loading image,that code is used for loading image from document directory if image is not there then i will display in one place holder image
cell = nil;
cell = (GMMCollectionViewCell *)[self.collectionView dequeueReusableCellWithReuseIdentifier:#"test" forIndexPath:indexPath];
cell.tag = indexPath.row;
docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path;
BOOL isImageLoaded = YES;
/////////////////////PROBLEM CODE:TAKING TOO MUCH TIME TO LOAD IMAGE //////////////////////
bookImage = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/%#", docPath, [[allbooksImage objectAtIndex:indexPath.row] lastPathComponent]]];
//////////////////////////////////////////////////////////////////////////////////
if(bookImage == nil){
isImageLoaded = NO;
}
if(!isImageLoaded){
[[cell grid_image] setImage:[UIImage imageNamed:#"App-icon-144x144.png"]];
} else{
[[cell grid_image] setImage:bookImage];
}
sharedManager.bookID=bookId;
return cell;
This is what iam expecting
But iam getting like this ,
App Loading time Without any scroll (first image:3 columns are empty)
App screen after first scroll (second image:last column displaying 2 images ,rest of the part still empty )
App screen after a long scroll (third image: last colum some images are there ,rest of the columns still empty)
App screen again scroll to top,so the first image changed
You can try the below code
[[cell grid_image] setImage:nil];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
bookImage = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/%#", docPath, [[allbooksImage objectAtIndex:indexPath.row] lastPathComponent]]];
dispatch_async(dispatch_get_main_queue(), ^{
if (bookImage)
{
[[cell grid_image] setImage:bookImage];
}
else
{
[[cell grid_image] setImage:[UIImage imageNamed:#"App-icon-144x144.png"]];
}
});
});
hope it will work for you.
I have a system which loads alot of large images from the web and displays them in custom table cells. On older devices the memory warnings happen pretty quickly so I implemented a system of deleting some from the table to try to combat this but it didn't work well enough (lots of images were deleted affecting the UI).
So I thought I could load all the images into the device's cache and then load them from there - I've implemented SDWebImage. This is great but I still havent solved the problem of memory allocation as the images are still being displayed all the time and therefore kept in memory - causing crashes.
I think I need to implement a system which shows the images (from the cache) if the cell is being displayed and hide it if the cell is not showing - I'm just stuck at how to build such a system.
Or is this not going to work? Can you really keep the apps memory low (and stop it having memory warnings / crashing) by removing images from its table cells? Or do I just need to carry on with my earlier solution and just delete images/cells until the memory warnings stop?
Updated with code
TableViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
currentIndexPath = indexPath;
ImageTableCell *cell = (ImageTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
ImageDownloader *download = [totalDownloads objectAtIndex:[indexPath row]];
if (cell == nil)
{
cell = [[[ImageTableCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: CellIdentifier] autorelease];
}
cell.imageView.image = download.image;
return cell;
}
return nil;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int t = [totalDownloads count];
return t;
}
ImageTableCell.m - Custom cell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
self.frame = CGRectMake(0.0f, 0.0f, 320.0f, 0.0f);
self.contentView.frame = CGRectMake(0.0f, 0.0f, 320.0f, 0.0f);
self.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
self.contentMode = UIViewContentModeScaleToFill;
self.autoresizesSubviews = YES;
self.contentView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
self.contentView.contentMode = UIViewContentModeScaleToFill;
self.contentView.autoresizesSubviews = YES;
[self.imageView drawRect:CGRectMake(0.0f, 0.0f, 320.0f, 0.0f)];
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.imageView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
self.imageView.opaque = YES;
}
return self;
}
ImageDownloader (implements SDWebImageManagerDelegate)
-(void) downloadImage // Comes from Model class
{
if (image == nil)
{
NSURL *url = [NSURL URLWithString:self.urlString];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
// Remove in progress downloader from queue
[manager cancelForDelegate:self];
if (url)
{
[manager downloadWithURL:url delegate:self retryFailed:YES];
}
}
}
- (void)cancelCurrentImageLoad
{
[[SDWebImageManager sharedManager] cancelForDelegate:self];
}
- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)_image
{
self.image = _image;
if ([self.delegate respondsToSelector:#selector(addImageToModel:)]) [self.delegate addImageToModel:self];
}
- (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error;
{
if ([self.delegate respondsToSelector:#selector(badImage)]) [self.delegate badImage];
}
After you download the images, dont keep the large images in memory. just create a small size of image(thumbnail) to display in the tableview and write the larger image to some directory.
you can create a thumbnail of your image using the following code.
CGSize size = CGSizeMake(32, 32);
UIGraphicsBeginImageContext(size);
[yourImage drawInRect:CGRectMake(0, 0, 32, 32)];
yourImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Instead of using [UIImage imageNamed:#""] , try [[[UIImage alloc] initWithContentsOfFile:#""] autorelease];
Edit:
Fine. I have gone through the SDWebImage.
Use NSAutoreleasePool wherever you find that a new thread has been spawned.
And one more solution would be, resize the image before saving to cache.
So basically once the image is downloaded it stays in it's SDWebImage instance and never gets released. You should save your image to iPhone's disk with:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *imagePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"image%d.jpg", rowNumber]; // you should have this set somewhere
[UIImageJPEGRepresentation(_image, 1.0) writeToFile:imagePath atomically:YES];
and keep just the path to it in your SDWebImage instance. Then in -cellForRowAtIndexPath: method instead of doing:
cell.imageView.image = download.image;
you should do something like:
UIImage *image = [[UIImage alloc] initwithContentsOfFile:download.imagePath];
cell.imageView.image = image;
[image release];
This will always load the image from the disk and since cell.imageView.image is a retained property, once it get's niled or reused, it will clean up the image from memory.
I would like to know how you are loading the images, are you using custom cells? If so please go through the Apple's UITableView Programming guide They are clearly saying us how to load the images. In that they are saying we should need to draw the images top avoid the memory issues.
Best example on how to load images are given in Apple's Sample Code LazyTableImages Please go through this too.
I have used kingfisher SDK and resized the server image to my custom cell size. It helps me a lot.
extension NewsCollectionViewCell {
func configure(with news: Articles) {
KF.url(URL(string:news?.urlToImage ?? ""), cacheKey: "\(news?.urlToImage ?? "")-").downsampling(size: imgNews.frame.size).cacheOriginalImage().set(to: imgNews)
}
}