In my iOS app, I have a UICollectionView where each cell contains an image. To prevent the view from taking a long time to load, I load each with a blank image and title before loading the image contents in a background task.
I logged which images are getting loaded through in the background async task, and it seems like the images of cells off screen get loaded first, followed by the cells at the top of the screen. This makes the app seem unresponsive, and I'd rather have the cells at the top take priority in terms of loading:
I also notice that once I start scrolling, the images in the cells suddenly start appearing, but they take much longer to appear on their own. Can anyone suggest strategies to control the ordering that UICollectionCells load in?
Here is my code:
Iterate over projects and add imageViews to an NSMutableArray projectContainers, which then gets turned into cells
for (NSDictionary *currentProject in projects)
{
// data entry
[projectIDs addObject: [currentProject objectForKey:#"id"]];
NSString *projectTitle = [currentProject objectForKey:#"title"];
id delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = [delegate managedObjectContext];
CustomLabel *cellLabel=[[CustomLabel alloc]init];
cellLabel.text = trimmedProjectTitle;
[titles addObject:projectTitle];
CGSize maxLabelSize = CGSizeMake(cellWidth,100);
CustomLabel *titleLabel = [[CustomLabel alloc]init];
// titleLabel styling
titleLabel.backgroundColor = [[UIColor blackColor]colorWithAlphaComponent:0.5f];
titleLabel.textColor =[UIColor whiteColor];
[titleLabel setFont: [UIFont fontWithName: #"HelveticaNeue" size:12]];
titleLabel.text = trimmedProjectTitle;
CGSize expectedLabelSize = [titleLabel.text sizeWithFont:titleLabel.font constrainedToSize:maxLabelSize lineBreakMode:NSLineBreakByWordWrapping];
CGRect labelFrame = (CGRectMake(0, 0, cellWidth, 0));
labelFrame.origin.x = 0;
labelFrame.origin.y = screenWidth/2 - 80 - expectedLabelSize.height;
labelFrame.size.height = expectedLabelSize.height+10;
titleLabel.frame = labelFrame;
// add placeholder image with textlabel
UIImageView *imagePreview = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, cellWidth, cellHeight)];
imagePreview.contentMode= UIViewContentModeScaleAspectFill;
imagePreview.clipsToBounds = YES;
[imagePreview setImage:[UIImage imageNamed:#"blank.png"]];
[imagePreview addSubview:titleLabel];
[imagePreview.subviews[0] setClipsToBounds:YES];
[projectContainers addObject: imagePreview];
// add project thumbnail images in async
dispatch_async(bgQueue, ^{
NSDictionary *imagePath = [currentProject objectForKey:#"image_path"];
NSString *imageUrlString = [imagePath objectForKey: #"preview"];
NSURL *imageUrl = [NSURL URLWithString: imageUrlString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:(imageUrl)];
UIImage *image = [[UIImage alloc] initWithData:(imageData)];
if(image){
NSLog(#"project with image: %#", projectTitle);
[imagePreview setImage: image];
}
BOOL *builtVal = [[currentProject objectForKey:#"built"]boolValue];
if(builtVal){
UIImageView *builtBanner =[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"built_icon.png"]];
builtBanner.frame = CGRectMake(screenWidth/2 -80, 0, 50, 50);
[imagePreview addSubview: builtBanner];
}
});
}
renders cells using the NSMutableArray projectContainers:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
// NSLog(#"cellForItemAtIndexPath");
static NSString *identifier = #"NewCell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
if(!reloadProjects){
UIImageView *preview = (UIImageView*) [cell.contentView viewWithTag:[[projectIDs objectAtIndex:indexPath.row]intValue]];
UIImageView *previewContent = [projectContainers objectAtIndex:indexPath.row];
// NSLog(#"fetching image tag %d", [[projectIDs objectAtIndex:indexPath.row]intValue]);
if (!preview)
{
previewContent.tag = [[projectIDs objectAtIndex:indexPath.row]intValue];
// NSLog(#"creating previewContent %li", (long) previewContent.tag);
[cell addSubview: previewContent];
}
[self.collectionView setBackgroundColor:collectionGrey];
cell.contentView.layer.backgroundColor = [UIColor whiteColor].CGColor;
return cell;
}
return cell;
}
EDIT: Working Solution
Thanks to rob mayoff for helping me come out with a solution. This is what I ended up doing, which loads the images much faster:
// add project thumbnail images in async
dispatch_async(imageQueue, ^{
NSDictionary *imagePath = [currentProject objectForKey:#"image_path"];
NSString *imageUrlString = [imagePath objectForKey: #"preview"];
NSURL *imageUrl = [NSURL URLWithString: imageUrlString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:(imageUrl)];
UIImage *image = [[UIImage alloc] initWithData:(imageData)];
if(image){
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"project with image: %#", projectTitle);
[imagePreview setImage: image];
});
}
BOOL *builtVal = [[currentProject objectForKey:#"built"]boolValue];
if(builtVal){
UIImageView *builtBanner =[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"built_icon.png"]];
builtBanner.frame = CGRectMake(screenWidth/2 -80, 0, 50, 50);
dispatch_async(dispatch_get_main_queue(), ^{
[imagePreview addSubview: builtBanner];
});
}
});
There are several things that code be improved in your code, but your chief complaint (“once I start scrolling, the images in the cells suddenly start appearing, but they take much longer to appear on their own”) is because you violated the commandment:
Thou shalt only access
thy view hierarchy
from the main thread.
Look at your code:
dispatch_async(bgQueue, ^{
...
[imagePreview addSubview: builtBanner];
You're manipulating the view hierarchy from a background thread. This is not allowed. For example, see the note at the bottom of this page, or the “Threading Considerations” in the UIView Class Reference.
You need to dispatch back to the main thread to update the view hierarchy.
Watch the Session 211 - Building Concurrent User Interfaces on iOS video from WWDC 2012. It talks in depth about how to do what you're trying to do, efficiently. See also this answer.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I am working with an IOS application. In my project have several UITableView with many rows, and each row has two images. When I scroll fast then It can't load cells smoothly. How can I scroll smoothly ???
N.B: I don't want to load all rows at a time.
Please Help
Edited:
Here is my code :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
float sca=appDelegate.deviceScaleFloat;
float XOffset=0*sca;
cell = [[UITableViewCell alloc] init];
cell.opaque = YES;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIImage *backImg;
backImg= [UIImage imageNamed:[NSString stringWithFormat:#"background-goal-collect%#.png",ipadExtention]];
backImg=[self scaleWithScaleFactor:backImg];
UIImageView *btnBuyImageView=[[UIImageView alloc]initWithImage:backImg];
btnBuyImageView.frame=CGRectMake(XOffset, 0, backImg.size.width, backImg.size.height);
[cell.contentView addSubview: btnBuyImageView];
for (int i=0; i<5 && indexPath.row*5+i<[catFishes count]; i++) {
int productIndex = (int)indexPath.row*5 + i;
DBProductAttributes *productAttributes = [allProductAttributes objectAtIndex:productIndex];
DBProductInfo *productInfo = [catFishes objectAtIndex:productIndex];
UIImage *frameImage = [UIImage imageNamed:[NSString stringWithFormat:#"background-element-%d%#.png",productAttributes.elementid,ipadExtention]];
frameImage=[self scaleWithScaleFactor:frameImage];UIImageView *frameView = [[UIImageView alloc] initWithImage:frameImage];
frameView.frame = CGRectMake((frameImage.size.width*i)+10*sca+5*i*sca, 5*sca , frameImage.size.width, frameImage.size.height);
frameView.userInteractionEnabled=YES;
[cell.contentView addSubview:frameView];
MyTapGestureRecognizer *tapGesture=[[MyTapGestureRecognizer alloc] init];
tapGesture.tag=productIndex;
[tapGesture addTarget:self action:#selector(buttonClicked:)];
[frameView addGestureRecognizer:tapGesture];
NSString *IconStr = [NSString stringWithFormat:#"i%db.png", productInfo.productid];
UIImage *btnImg = [UIImage imageNamed:IconStr];
if(![self isProductPurchesed:productInfo.productid])
{
if([ITIWAppDelegate blackimageforstore]>0)
{
btnImg = [self getBlackAndWhiteVersionOfImage:btnImg];
}
}
UIImageView *imageIconView;
imageIconView = [[UIImageView alloc] initWithImage:btnImg];
imageIconView.frame = CGRectMake(frameView.frame.origin.x+frameImage.size.width-64*sca, frameView.frame.origin.y/*+frameImage.size.height-64*sca*/ , 64*sca, 64*sca);
imageIconView.opaque = YES;
[cell.contentView addSubview:imageIconView];
UILabel *name;
name = [[UILabel alloc] initWithFrame:CGRectMake(frameView.frame.origin.x, frameView.frame.origin.y+62*sca, frameImage.size.width-0*sca, 18.0f*sca)];
name.text = productInfo.product_name;
name.font = [UIFont fontWithName:#"Georgia" size:12.0f*sca];
name.adjustsFontSizeToFitWidth = YES;
name.textAlignment = NSTextAlignmentCenter;
if(![self isProductPurchesed:productInfo.productid])
name.backgroundColor=[UIColor grayColor];
else
name.backgroundColor = [UIColor colorWithRed:colorCodeDragonBook[productAttributes.elementid-1][0]/255.0f green:colorCodeDragonBook[productAttributes.elementid-1][1]/255.0f blue:colorCodeDragonBook[productAttributes.elementid-1][2]/255.0f alpha:1.0f];
name.textColor = [UIColor colorWithWhite:1.0 alpha:1.0];
//name.shadowColor = [UIColor blackColor];
name.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
[cell.contentView addSubview:name];
NSArray *otherElements = [productAttributes.otherElementid componentsSeparatedByString:#","];
int k=0;
UIImage *habitatFlag = [UIImage imageNamed:[NSString stringWithFormat:#"flag-%d.png",productAttributes.elementid]]; UIImageView *habitatFlagView = [[UIImageView alloc] initWithImage:habitatFlag];
habitatFlagView.frame = CGRectMake(frameView.frame.origin.x-1*sca, frameView.frame.origin.y-1*sca , 15*sca, 22*sca);
[cell.contentView addSubview:habitatFlagView];
k+=15;
for (int i=0; i<[otherElements count]; i++) {
int otherElementid = [[otherElements objectAtIndex:i] intValue];
if(otherElementid==productAttributes.elementid) continue;
UIImage *habitatFlag = [UIImage imageNamed:[NSString stringWithFormat:#"flag-%d.png",otherElementid]]; UIImageView *habitatFlagView = [[UIImageView alloc] initWithImage:habitatFlag];
habitatFlagView.frame = CGRectMake(frameView.frame.origin.x+k*sca-1*sca, frameView.frame.origin.y-1*sca , 15*sca, 22*sca);
[cell.contentView addSubview:habitatFlagView];
k+=15;
}
}
return cell;
}
Problem is occurring when the cells are going to off screen, the tableview release all cells of off screen. And when after that I want to scroll the cells are reloading. I think it is not optimal to load cells. But I don't know how to optimize this.
the best you have to do is load the images asynchronously, and not in the main thread.
If you want, you can use my ImageLoader project : https://github.com/celian-m/ImageLoader/blob/master/ImageLoader.swift
All you have to do is using CMImageView instead of UIImageView.
Then you can do [myImageView assignImageFromUrl:YOUR_URL]
This will load your images in the background thread, in FIFO mode, and save it in memory and disk cache ( i.e. : you need to load each image only 1 time ).
You can make use of SDWebImageCache so that it'll cache the images in disk and the loading of images becomes much faster.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
dispatch_async(queue, ^{
//set image here
});
What about setting the image async in the tableview callback:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
May I ask whether the images are web images or local images?
I'm using the QuickBlox Framework to build a chatting application. Currently, when the chat view opens up, everything looks great.
However, when the users begins to scroll up and down the chat history, some of the cells begin to change (for example, they'll show an image which should be placed in a different row).
Below is my code for cellForRowAtIndexPath, if anyone can tell me what I'm doing wrong
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
QBChatMessage *message = [[ChatService shared] messagsForDialogId:self.dialog.ID][indexPath.row];
if (message.attachments.count > 0) {
ImageTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ImageCellIdentifier];
[cell configureCellWithImage:message];
cell.backgroundColor = [UIColor whiteColor];
return cell;
} else {
ChatMessageTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ChatMessageCellIdentifier];
[cell configureCellWithMessage:message];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
}
EDIT Please see below my ImageTableViewCell configureCellWithImage method:
- (void) configureCellWithImage:(QBChatMessage*)message {
NSString *time = [message.dateSent timeAgoSinceNow];
if ([QBSession currentSession].currentUser.ID == message.senderID) {
// Message was sent by me
NSData *imageData = [FTWCache objectForKey:[NSString stringWithFormat:#"%#", [message.attachments[0] valueForKey:#"ID"]]];
if (imageData) {
// image is already downloaded
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [UIImage imageWithData:imageData];
UIImageView *cellImage = [[UIImageView alloc] init];
[self.backgroundImageView setFrame:CGRectMake(320-155, 10, 140, 140)];
cellImage.frame = CGRectMake(7, 7, 120, 120);
[cellImage setContentMode:UIViewContentModeScaleAspectFill];
cellImage.clipsToBounds = YES;
cellImage.layer.cornerRadius = 5;
cellImage.image = image;
self.backgroundImageView.image = aquaBubble;
[self.backgroundImageView addSubview:cellImage];
[self.contentView addSubview:self.backgroundImageView];
});
} else {
// downloads the image and displays as above
}
} else {
// Message was sent by another user
NSData *imageData = [FTWCache objectForKey:[NSString stringWithFormat:#"%#", [message.attachments[0] valueForKey:#"ID"]]];
if (imageData) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [UIImage imageWithData:imageData];
UIImageView *cellImage = [[UIImageView alloc] init];
[self.backgroundImageView setFrame:CGRectMake(padding/2, padding+5, 140, 140)];
cellImage.frame = CGRectMake(13, 7, 120, 120);
[cellImage setContentMode:UIViewContentModeScaleAspectFill];
cellImage.layer.cornerRadius = 5;
cellImage.clipsToBounds = YES;
cellImage.image = image;
self.timeLabel.frame = CGRectMake(20, self.backgroundImageView.frame.size.height + 20, 80, 20);
self.timeLabel.text = [NSString stringWithFormat:#"%#", time];
[self.timeLabel setFont:[UIFont systemFontOfSize:10.0]];
[self.timeLabel setTextColor:[UIColor blackColor]];
[self.contentView addSubview:self.timeLabel];
self.nameAndDateLabel.textAlignment = NSTextAlignmentLeft;
QBUUser *sender = [ChatService shared].usersAsDictionary[#(message.senderID)];
NSInteger loginForColor = [sender.login integerValue];
loginForColor = loginForColor % 255;
self.nameAndDateLabel.text = [NSString stringWithFormat:#"%#", sender.fullName];
self.backgroundImageView.image = orangeBubble;
[self.backgroundImageView addSubview:cellImage];
[self.contentView addSubview:self.backgroundImageView];
});
} else {
// downloads the image and displays as above
}
}
}
Cells get reused. Therefore you must always set/reset all properties of the cell each time.
For every if statement that sets a cell's property, there must be an else statement that resets the same property - even if it just clears the value.
Also you must avoid adding subviews over and over each time the cell is used. You have code that creates and adds an image view to the cell. But you keep adding new image views over and over. Just add it once if needed. If it's already there, update it with the new image instead of adding a new one.
The error should be on the functions configureCellWithImage and configureCellWithMessage.
I didn't see the code of those functions, but i bet that you didn't clean the image content on the configureCellWithMessage.
I am using iCarousel custom control to show image from web that consumed with JSON data.
Here is my codes to show image in iCarousel
to Load JSON Data in ViewDidLoad
JSONLoader *jsonLoader = [[JSONLoader alloc]init];
self.items = [[NSMutableArray alloc]init];
[self.items removeAllObjects];
self.items = (NSMutableArray *) [jsonLoader loadJSONDataFromURL:[NSURL URLWithString:#"https://public-api.wordpress.com/rest/v1/sites/www.myWebsite.com/posts?category=blog&page=1"]];
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(AsyncImageView *)view
{
MMSPLoader *mmObject = [self.items objectAtIndex:index];
view = [[AsyncImageView alloc]initWithFrame:CGRectMake(0, 0, 250.0f, 250.0f)];
view.layer.borderColor = [UIColor whiteColor].CGColor;
view.layer.borderWidth = 0.3f;
view.image=[UIImage imageNamed:#"page.png"];
view.imageURL = [NSURL URLWithString:[mmObject featureImageUrl]];
return view;
}
That can show image correctly. My case is when i tap on that image , i want to show that image in FULL SCREEN. So i used GGFullScreenImageViewController.
However when i tap on that Image to show FULL SCREEN , i retrieved Image URL and show in GGFullScreenImageViewController. It's fine but , i don't want to retrieve from that URL because it downloading image from web again and slowing to show.
In my idea , i saved that image when tap on image in iCarousel and show it in GGFullScreenImageViewController.
So i don't need to download image again.
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index
{
dispatch_queue_t myqueue = dispatch_queue_create("com.i.longrunningfunctionMain", NULL);
dispatch_async(myqueue, ^{
UIApplication *apps = [UIApplication sharedApplication];
apps.networkActivityIndicatorVisible = YES;
MMLoader *mmObject = [self.items objectAtIndex:index];
NSData *data = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:mmObject.featureImageUrl]];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:data]];
GGFullscreenImageViewController *vc = [[GGFullscreenImageViewController alloc] init];
vc.liftedImageView = imageView;
dispatch_async(dispatch_get_main_queue(), ^{
apps.networkActivityIndicatorVisible = NO;
[self presentViewController:vc animated:YES completion:nil];
});
});
NSLog(#"%i",index);
}
So should i save to local file or is there any others nice idea?
Really you should use a library to save the image when you initially download it. AsyncImageView isn't necessarily the best choice as it just caches in memory.
That said, at the moment you can just get the image from the view. This isn't ideal, and you should save it to disk - just sooner rather than later. Look at, perhaps, SDWebImage for that.
To get the image from the view (typed in browser so verify syntax and API usage...):
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index
{
AsyncImageView *view = (AsyncImageView *)[carousel itemViewAtIndex:index];
UIImageView *imageView = [[UIImageView alloc] initWithImage:view.image];
GGFullscreenImageViewController *vc = [[GGFullscreenImageViewController alloc] init];
vc.liftedImageView = imageView;
[self presentViewController:vc animated:YES completion:nil];
}
I'm using iCarousel library in my small app. I'm getting the image urls via web service and placing those images to the iCarousel library.
First I created a UIView and added iCarousel as respective class to it. Latter set the datasourse and delete gate to same class.
Now everything looks cool and I could see the images but I couldn't swipe the images.
Following is my code.
- (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
//return the total number of items in the carousel
return [galleryItems count];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
UILabel *label = nil;
//create new view if no view is available for recycling
if (view == nil)
{
//NSString *ImageURL = #"YourURLHere";
//NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageURL]];
//imageView.image = [UIImage imageWithData:imageData];
view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 200.0f)];
//[((UIImageView *)view) setImageWithURL:[NSURL URLWithString:#"http://www.pizzatower.com/img/icons/Pizza-icon.png"]];
NSURL *imageURL = [[galleryItems objectAtIndex:index] galleryURL];
[((UIImageView *)view) setImageWithURL:imageURL];
view.contentMode = UIViewContentModeScaleAspectFit;
label = [[UILabel alloc] init];
label.backgroundColor = [UIColor clearColor];
label.font = [label.font fontWithSize:20];
label.tag = 1;
// label.contentMode = UIViewContentModeBottom;
// label.frame = CGRectMake(
// self.view.frame.size.width - label.frame.size.width,
// self.view.frame.size.height - label.frame.size.height,
// label.frame.size.width,
// label.frame.size.height );
//label.bounds = view.bounds;
[view addSubview:label];
}
else
{
//get a reference to the label in the recycled view
label = (UILabel *)[view viewWithTag:1];
}
//set item label
//remember to always set any properties of your carousel item
//views outside of the `if (view == nil) {...}` check otherwise
//you'll get weird issues with carousel item content appearing
//in the wrong place in the carousel
//label.text = [[galleryItems objectAtIndex:index] galleryDescription];
return view;
}
I've used same code base and it worked in some other project. No idea what am I missing here. Can someone please enlighten me.
You UIView containing the carousel may have userInteractionEnabled set to NO.
Another possibility is that the UIView containing the carousel may be smaller than the carousel and so be blocking interaction. Try setting its background color to see its true size.
I'd suggest delete your nib or the ViewController in your storyboard and re-create it. I had the same issue once and after re-creating it it worked. Make sure you center your UIView when placing it (both horizontally and vertically)
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)
}
}