UITableview dequeueReusableCellWithIdentifier and UIImageView issue? - iphone

I'm loading asynchronously images into UITableView and each row has different image. the problem is when i scroll the UITableView the previous images are loaded again in the new rows and after a while these images are changed to the correct ones (this problem is gone when i removed dequeueReusableCellWithIdentifier but it reduces the performance)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCellFriends" owner:self options:nil];
cell = [nibObjects objectAtIndex:0];
nibObjects = nil;
}
UIImageView *avatar = (UIImageView *)[cell viewWithTag:100];
UILabel *name = (UILabel *)[cell viewWithTag:2];
UILabel *city = (UILabel *)[cell viewWithTag:3];
EntryProfile *entryForRow = [entriesProfileArray objectAtIndex:indexPath.row];
avatar.image = nil;
NSString *strLinkImage = [entryForRow.apiAvatarPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSArray *args = [NSArray arrayWithObjects:avatar,strLinkImage,[NSString stringWithFormat:#"%d",indexPath.row], nil];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(addPhotoWithImageView:) object:args];
[operationQueue addOperation:operation];
[operation release];
// Set the name of the user
name.text = entryForRow.name;
return cell;
}
// Method
- (void)addPhotoWithImageView:(NSArray *)args
{
UIImageView *imageView = (UIImageView *)[args objectAtIndex:0];
NSString *strLinkImage = (NSString *)[args objectAtIndex:1];
NSString *index = (NSString *)[args objectAtIndex:2];
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.example.com%#",strLinkImage]]];
UIImage *imageField = [[UIImage alloc] initWithData:imageData];
if (imageData) {
[imageView performSelectorOnMainThread:#selector(setImage:) withObject:imageField waitUntilDone:NO];
}
[imageField release];
}

If there are people still facing this issue, I see the answers above do not really help.
2 steps to take:
You need to "cancel" the asynchronous request that is ongoing for the image view and then set a new image asynchronously with a placeholder image till the async request goes through
Ideally, do not send your async request while the user is scrolling through
(tableview/scrollview is decelerating) - try to avoid making new requests and just set the placeholder in such cases (unless you have a way of confidently knowing that the request was done before and hence it is likely that the image is found in the local disk cache for the image url)
The above 2 steps will keep your functionality as intended and give you good performance too.

Try to implement your own UItableViewCell and set the all your cell properties to nil in the PrepareForReuse method.
I would also recomend you to take a look at this project in order to load asynchronously remote images:
https://github.com/nicklockwood/AsyncImageView
I used it with great success in different projects.

in cellForRowAtIndexPath,
before you call:
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(addPhotoWithImageView:) object:args];
force yourCell imageView.image to nil
imageView.image = nil;
this way you clean the contents image of old cell, then it will be redraw with right image when it's loaded...
EDIT
maybe nil doesn't works...
try a placeHolder image, a real png file (with a color, for test, then it may be a transparent file...)

Had a similar issue the other day and the way I solved it was the following ( I didn't notice any performance reduction ).
UITableViewCell *cell;
if (cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// rest of the code
}
So basically invoke the dequeue inside the if.

This is a classic problem, easily solved.
In your cellForRowAtIndexPath: implementation, you can include code like this:
if (!tableView.decellerating) {
// set avatar.image to its correct UIImage value how ever you like,
// in the background if necessary
} else {
// table is scrolling...
avatar.image = nil;
}
And then implement this method in your view controller:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSArray *visibleCells = [self.tableView visibleCells];
[visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UITableViewCell *cell = (UITableViewCell *)obj;
UIImageView *avatar = (UIImageView *)[cell viewWithTag:100];
avatar.image = ...; // Do what you need to do to set the image in the cell
}];
}
You will also need to add UIScrollViewDelegate to the list of protocols your view controller adopts.
The idea here is that as long as the table view is moving, don't bother loading the images. Your performance will skyrocket.

Related

iOS - UITableView scrollbar goes out of screen

I am new to iOS and i have some serious problem i don't know why this happened, i also tried to find out the reason behind this but i failed.
I am creating a app in iPhone which take tweet of particular user using twitter api, i have no problem in getting the json data, also able to display it in UITableView in iOS 6 using storyboard, i had changed the row height to 200 px, so that i can display user name text tweet and image.
But when I scroll up or down the UITableViewCell the scrollbar goes out of the screen.
What may be the reason?how can I fix it?
ScreenShots
First it shows like this when the app loads the scrollbar takes the full screen
then when i scroll it get out of the screen
at last it completely gets out of screen
UITableView delegate methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *name,*time,*tweet,*image;
name=[[NSMutableArray alloc]init];
time=[[NSMutableArray alloc]init];
tweet=[[NSMutableArray alloc]init];
image=[[NSMutableArray alloc]init];
static NSString *CellIdentifier = #"SimpleTableItem";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
for (User *userModel in self.user) {
[name addObject:userModel.nameStr];
[image addObject:userModel.imageStr];
}
for (Json *jsonModel in self.json) {
[tweet addObject:jsonModel.tweetStr];
[time addObject:jsonModel.timeStr];
}
// Fetch using GCD
dispatch_queue_t downloadThumbnailQueue = dispatch_queue_create("Get Photo Thumbnail", NULL);
dispatch_async(downloadThumbnailQueue, ^{
NSString *imageURL=[image objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *image = (UIImageView *)[cell.contentView viewWithTag:5];
image.image=img;
[cell setNeedsLayout];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[spinner stopAnimating];
});
});
self.labelName = (UILabel *)[cell.contentView viewWithTag:10];
[self.labelName setText:[NSString stringWithFormat:#"%#", [name objectAtIndex:indexPath.row]]];
self.labelTime = (UILabel *)[cell.contentView viewWithTag:11];
[self.labelTime setText:[NSString stringWithFormat:#"%#", [time objectAtIndex:indexPath.row]]];
self.labelTweet = (UILabel *)[cell.contentView viewWithTag:12];
[self.labelTweet setText:[NSString stringWithFormat:#"%#", [tweet objectAtIndex:indexPath.row]]];
cell.imageView.frame=CGRectMake(0, 0, 40, 40);
cell.textLabel.lineBreakMode=NSLineBreakByWordWrapping;
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGFloat rowHeight=self.labelName.frame.size.height+self.labelTime.frame.size.height+self.labelTweet.frame.size.height+50;
return rowHeight;
}
Well scroll goes out of the screen means the tableView frame is larger than the screen size
Set the frame of tableview properly using setFrame: method
I think you have to check the height of the UITableView. Show the screenshot.
Here the ScrollBar goes down as the Height of TableView is larger then the UIView in which you are adding TableView. Adjust the frame according to your view then the ScrollBar will not disappear.

iPhone: UITableView performance issue with imageWithContentsOfFile

I have performance issue in UITableView scrolling when I use imageWithContentsOfFile instead of imageNamed. Following is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UniversalAppAppDelegate *delegate = (UniversalAppAppDelegate *) [[UIApplication sharedApplication] delegate];
NSDictionary *res = [[delegate comments] objectAtIndex:indexPath.section];
if (cell == nil)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
......
// This line has a problem...when I use imageNamed here instead of imageWithContentsOfFile, it works absolutely fine
imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:imagepath]];
[imageView setFrame: CGRectMake(30, 10, 55, 55)];
[sectionHeaderView addSubview:imageView];
......
cell.backgroundView = sectionHeaderView;
return cell;
I have also tried caching image but still same issue. Only imageNamed works perfectly fine but I can't use it as I need to specify path for image stored in documents folder and changes dynamically. Could anyone please tell me how could I resolve this issue?
Caching code:
NSMutableDictionary *thumbnailCache = [[NSMutableDictionary alloc] init];
- (UIImage*)thumbnailImage:(NSString*)fileName
{
UIImage *thumbnail = [thumbnailCache objectForKey:fileName];
if (nil == thumbnail)
{
NSString *thumbnailFile = [self filePath:fileName];
thumbnail = [UIImage imageWithContentsOfFile:thumbnailFile];
[thumbnailCache setObject:thumbnail forKey:fileName];
}
return thumbnail;
}
Usage:
imageView = [[UIImageView alloc] initWithImage:[delegate thumbnailImage:[res objectForKey:#"ImageName"]]];
You could try building an array of UIImages in the viewDidLoad method of the controller, and use the images in that array in your cellForRowAtIndexPath.
This will get rid of the need to call imageWithContentsOfFile for each image when drawing the tableview.
You could for example build the array in a seperate thread, and show a loading indicator while this happens. It would mean a small loading time at first, but a smooth scrolling tableview.

set UITableView image

I am trying to load locally stored images in a background thread and set them as UITableViewCell images. I keep getting an exception and don't really know how to approach it.
Here is my code:
- (void)loadImageInBackground:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *path = [[tableImages objectAtIndex:indexPath.section]objectAtIndex:indexPath.row];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:#"png"]];
[self performSelectorOnMainThread:#selector(assignImageToImageView:) withObject:img waitUntilDone:YES];
[img release];
[pool release];
}
- (void) assignImageToImageView:(UIImage *)img
{
UITableViewCell *cell = (UITableViewCell *)[self.tableView viewWithTag:0];
((UIImageView *)cell.imageView).image = img;
}
Anyone have any ideas? Thanks to all!
As is mention in your error you are trying to set image to object of class UITableView. It is impossible.
In your code it is error here:
UITableViewCell *cell = (UITableViewCell *)[self.tableView viewWithTag:0];
By default all views has default view tag set to 0. When your are calling that method to your tableView it returns you first UIView with tag == 0. I think you didn't change default tag of your UITableView *tableView so that method returns you tableView and not UITableViewCell.
In this case, you need to choose right way to access appropriate UITableViewCell *cell;.

iOS 4.2: Subview in Tableview acting weird

I'm semi new to developing for iOS.
And when i try to customize a tableview cell to put an image under the description text, it works, until i scroll, then it gets all messed up.
When i scroll down, then back up, the image is in a cell it shouldn't be in. :/
I deleted the code because it wasn't working. But what i was doing was creating a UIView with a frame, with a UIImageView inside it. Then i add it to cell.contentView.
Which works, until i scroll.
So could someone please help :/
Edit: here is what it looks like for anyone interested, i got it working. (Thanks Jonathan)
#define LEVEL_TAG 1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UIImageView *showLevel;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
showLevel = [[[UIImageView alloc] initWithFrame: CGRectMake(65, 50, 0, 11)] autorelease];
showLevel.tag = LEVEL_TAG;
[cell.contentView addSubview: showLevel];
} else {
showLevel = (UIImageView *)[cell.contentView viewWithTag:LEVEL_TAG];
}
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *levelArray = [dictionary objectForKey:#"Levels"];
NSString *levelValue = [levelArray objectAtIndex:indexPath.row];
UIImage *levelImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource: levelValue ofType:#"png"]];
CGImageRef starImage = levelImage.CGImage;
NSUInteger levelWidth = CGImageGetWidth(starImage);
showLevel.frame = CGRectMake(65, 50, levelWidth, 11);
showLevel.image = levelImage;
return cell;
}
It's because of the way the tableView reuses old cells, rather than creating new ones every time the user scrolls, which would make everything slower, and less efficient.
Have a look at Listing 5-3 on the Apple Documentation about customizing UITableViewCells, it shows you how to set up the cell, within the if statement where the cell is actually created, and not where the cell could have been reused.

iPhone: How to reduce the memory size of the pic

hi i am working with Twitter, i got problem with images, i have tweet view have UITableview, it contains all tweet with user photo, if i load those photos in each cell when i scroll the UITableview, it scrolling very very slowly please suggest me to reduce the memory size of photos and scroll the UITableView fast.
i heard the thumbnail can reduce the memory size, does it.(if not which method i have to choose and what thumbnail method do's) if so how to do that in this code (table view cell code)
//Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStyleGrouped reuseIdentifier:nil] autorelease];
UIImageView *myImage = [[UIImageView alloc]initWithFrame:CGRectMake(6,10,58,60)] ;
NSURL *url = [NSURL URLWithString:[(Tweet*)[tweetArray objectAtIndex:indexPath.row] image_url]]; //here Tweet is other class and image_url is method in the Tweet class
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[myImage setImage: [UIImage imageWithData:data]];
[cell.contentView addSubview:myImage];
[myImage release];
}
return cell;
}
Thank you,
please suggest me
The reason for slow scroll of the table view is that you are trying to get the image data for every cell on the main thread. UI is getting blocked while it is going to fetch the image data from the URL and, According to me its not good to download images on main thread while table view is also loaded.
Instead of using this approach, You should have to use NSOperationQueue, NSOperation and NSThread for async load of images to the appropriate cell.
If you need more help or simple code like 2-3 function to download images async...
Here are the functions....
Where you are parsing/getting the values only call [self startLoading]; It will load images without blocking UI.
- (void) startLoading {
NSOperationQueue *queue = [[[NSOperationQueue alloc]init]autorelease];
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(loadImagesInBackground) object:nil];
[queue addOperation:op];
[op release];
}
-(void) loadImagesInBackground {
int index = 0;
for (NSString urlString in [(Tweet*)[tweetArray objectAtIndex:indexPath.row] image_url]) {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[myImageArray addObject: [UIImage imageWithData:data]];
index++;
if(index/3==0)
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStyleGrouped reuseIdentifier:nil] autorelease];
UIImageView *myImage = [[UIImageView alloc]initWithFrame:CGRectMake(6,10,58,60)] ;
[myImage setImage: [myImageArray objectAtIndex:indexpath.row]];
[cell.contentView addSubview:myImage];
[myImage release];
}
return cell;
}
This article (Download images for a table without threads) might have your answer.
There's also this sample in the iOS dev library, which shows how to load images assynchronously, as quickly as possible.
For sure you want to download your images lazily and asynchronously as described by other answerers.
You also probably want to resize it to your thumbnail size and store it in memory at the smaller size. See this article for tutorial and actual usable library code to do that (scroll down past the rant about copy-and-paste development).
EDIT: You clearly need more help with the background download part. Here's how I do it.
Install ASIHTTPRequest, which is a third party library that GREATLY simplifies HTTP client work. Follow the instructions to install it in your project, and make sure you put #include "ASIHTTPRequest.h" at the top of your .m file.
Then in your tableView: cellForRowAtIndexPath:, go:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
UIImageView *myImage = [[UIImageView alloc]initWithFrame:CGRectMake(6,10,58,60)] ;
[cell.contentView addSubview:myImage];
[myImage release];
}
// so at this point you've got a configured cell, either created fresh
// or dequeued from the table's cache.
// what you DON'T have is a pointer to the uiimageview object. So we need to
// go get it.
UIImageView *theImageView;
for (UIView *view in cell.contentView.subviews) {
if ([view isKindOfClass:[UIImageView class]) {
theImageView = view;
break;
}
}
NSURL *url = [NSURL URLWithString:[(Tweet*)[tweetArray objectAtIndex:indexPath.row] image_url]]; //here Tweet is other class and image_url is method in the Tweet class
ASIHTTPRequest *req = [ASIHTTPRequest requestWithURL:url];
[req setCompletionBlock:^{
NSData *data = [req responseData];
// This is the UIImageView we extracted above:
[theImageView setImage: [UIImage imageWithData:data]];
}];
// this will start the request in the background, and call the above block when done.
[req startAsynchronous];
}
// Then return the cell right now, for the UITableView to render.
// The image will get filled in later when it returns from the server.
return cell;
}