1

I have a tableview with about 20 cells with images that are downloaded from the net.

Right now it is choppy when scrolling and it needs to be make more smooth.

My method is:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    CustomCell *cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {

        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];

        for (id currentObject in topLevelObjects){
            if ([currentObject isKindOfClass:[UITableViewCell class]]){
                cell =  (CustomCell *) currentObject;
                break;
            }
        }
    }

    NSDictionary *article = [entriesArray objectAtIndex:[indexPath row]];
    NSString *title = [article objectForKey:@"title"];
    NSString *date = [article objectForKey:@"publishedDate"];
    NSString *dateEdited = [date substringToIndex:16];

    cell.nameLabel.text  = title;
    cell.dateLabel.text  = dateEdited;

    NSURL *myURL=[NSURL URLWithString:[self.picturesArray objectAtIndex:indexPath.row]]; 
    NSData *myData1 = [[NSData alloc] initWithContentsOfURL:myURL];
    UIImage *myImage = [[UIImage alloc] initWithData:myData1]; 

    cell.imageView.image = myImage;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
    cell.textLabel.numberOfLines = 2;

    return cell;
}

Any advice?

Jon
  • 4,732
  • 6
  • 44
  • 67
  • possible duplicate of [Tricks for improving iPhone UITableView scrolling performance?](http://stackoverflow.com/questions/1352479/tricks-for-improving-iphone-uitableview-scrolling-performance) In this case do your own drawing, load images on the background, and cancel loading when the cell goes out of sight. HJCache implements background loading and interruptions to load table images. See http://www.markj.net/hjcache-iphone-image-cache/ – Jano Sep 13 '11 at 10:54

6 Answers6

2

There's a few issues. Firstly, you have some memory leaks. myData1 and myImage are created but not released.

However, my guess would be that the big performance hits relates to you creating the UIImage. Move this out of this cell request method. Perhaps create a cache of images that you can draw upon. For example, have them as static UIImage objects created in your initialize method.

I think this will solve the bulk of your performance issues. One final tweak would be to remove your for loop from within this method. Perhaps create an NSDictionary so that objects can be looked up directly.

Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
  • Thanks, I don't need that for loop, I found it on this site and it was the only way that my custom cell was loading. How can I do without it? – Jon Sep 13 '11 at 10:48
  • When I do that it crashes with error: `*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'` – Jon Sep 13 '11 at 10:57
  • Create the custom cell using it's init method. So basically, you call the dequeueReusable method. If that returns nil, create the cell there and then. dequeueReusable is invoked so that you can avoid creating a cell class unless you have to. – Max MacLeod Sep 13 '11 at 11:01
  • So I have two methods? `- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier` and `cellForRowAtIndexPath`? – Jon Sep 13 '11 at 11:05
  • No, you only need one method. cellForRowAtIndexPath must return a cell. When you call the dequeue, that will either give you an existing cell, which you populate with your data and return. Or, it will give you nil. If it gives nil, then within cellForRowAtIndexPath you must create a new cell - probably using initWithStyle - then you return that. – Max MacLeod Sep 13 '11 at 13:20
2

It doesn't appear that you are running the network request on a background thread, which is what you need to be doing. Not only from a performance standpoint but from a stability standpoint as well. What happens if the server you are making the network request to is down? I am not sure on how initWithContentsOfURL: handles failures but your code doesn't seem to be checking for a failure from that method, so it looks like your program will crash as a result of network problems.

Implementing the fetch on a background thread will not cause the main thread to block, allowing your UITableView to scroll much more seamlessly, however the tradeoff for this will be that table view images may be blank for a little while as it waits for the background thread to complete the network request.

Stunner
  • 12,025
  • 12
  • 86
  • 145
0

You should not be downloading the image files on the main event loop.

Try to use some background downloader like SDWebImage.

Thilo
  • 257,207
  • 101
  • 511
  • 656
0

This is how you load custom cells

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    CustomCell *cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
cell = [CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIndetifier];
    }

    NSDictionary *article = [entriesArray objectAtIndex:[indexPath row]];
    NSString *title = [article objectForKey:@"title"];
    NSString *date = [article objectForKey:@"publishedDate"];
    NSString *dateEdited = [date substringToIndex:16];

    cell.nameLabel.text  = title;
    cell.dateLabel.text  = dateEdited;

    NSURL *myURL=[NSURL URLWithString:[self.picturesArray objectAtIndex:indexPath.row]]; 
    NSData *myData1 = [[NSData alloc] initWithContentsOfURL:myURL] autorelease];
    UIImage *myImage = [[UIImage alloc] initWithData:myData1] autorelease]; 

    cell.imageView.image = myImage;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
    cell.textLabel.numberOfLines = 2;

    return [cell autorelease];
}
Vlad
  • 3,346
  • 2
  • 27
  • 39
  • - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier (edited my post as well) – Vlad Sep 13 '11 at 11:01
  • Do I have to make a new method with that or what? – Jon Sep 13 '11 at 11:03
  • That is the init method of any UITableViewCell. Any subclasses (in your case, the custom cells) will be created using that constructor. How did u create your custom cells? – Vlad Sep 13 '11 at 11:07
  • My custom cells are subclass of UITableViewCell and has a XIB. Can you just take the method from my question and modify that? – Jon Sep 13 '11 at 11:08
  • 1
    I tried that but its not loading any cell now. Its just blank. – Jon Sep 13 '11 at 11:17
  • It also crashed here with EXC BAD ACCESS `CustomCell *cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];` – Jon Sep 13 '11 at 11:18
  • http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7 – Vlad Sep 13 '11 at 11:23
0

The first place I would check (and one that's easy to miss) is to make sure the reuse identifier of your cell is set correctly (to "Cell" in your case)in your CustomCell.xib file.

Example:

enter image description here

To create your cell. assuming you have a nib called CustomCell.xib, you should create an IBOutlet in your view controller:

@property (nonatomic, retain) IBOutlet UITableViewCell * customCell;

and then in your CustomCell nib, set the File's Owner to be your view controller, and hook up the cell to the File's Owner custom cell outlet.

To create your cell, use the following code:

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if(cell == nil)
    {
        [[NSBundle mainBundle] loadNibNamed: @"CustomCell" owner: self options: nil];
        cell = self.customCell;
        self.customCell = nil;
    }
    // Customise your cell here
    return cell;    
}
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
0

You need to lazily load your images. This means loading them in another thread. Apple has excellent sample code that demonstrates this.

Alastair Stuart
  • 4,165
  • 3
  • 36
  • 33