views:

449

answers:

2

I've implemented ABTableViewCell because my iPhone application had a hard time scrolling a tableview with custom cells.

Everything is working fine and dandy, except for one thing and I can't figure out how to solve it. The UITableView gets its data from an array of parsed XML elements, this data is then fed to the CustomCell by my RootViewController using cellForRowAtIndexPath. Every CustomCell of the UITableView which is in view is drawn correctly, when I scroll down, however, I see duplicate entries which only change to the correct cell title when selected.

So out of 15 table entries, only 9 or 10 have the correct data when the view is drawn en the 5 or 6 which are out of view only show the correct data when selected. Can anybody tell what I'm doing wrong or what I'm missing here? Thanks!

RootViewController.h:

#import <UIKit/UIKit.h>

@interface RootViewController : UITableViewController {
    MyAppDelegate *appDelegate;
  UIToolbar *toolbar;
  NSArray *tableDataArray;
}

@property (nonatomic, retain) NSArray *tableDataArray;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil tableDataSource:(NSArray*)tableData;

@end

RootViewController.m:

#import "CustomCell.h"
#import "MyAppDelegate.h"
#import "RootViewController.h"
#import "DetailViewController.h"

@implementation RootViewController
@synthesize tableDataArray;

- (void)viewDidLoad {
  [super viewDidLoad];
}

//Override the default initWithNibName method
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil tableDataSource:(NSArray*)tableData {
  if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
    // Custom initialization
    tableDataArray = [tableData retain];   
  }
  return self;
}

-(void)viewWillAppear:(BOOL)animated {
  appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];    
  [super viewWillAppear:animated];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return [self.tableDataArray count];
}

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

    CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CustomCellIdentifier];
    if(cell == nil) {
        cell = [[[CustomCell alloc] initWithFrame:CGRectZero reuseIdentifier:CustomCellIdentifier] retain];
    }
  cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  NSUInteger row = [indexPath row];
  NSDictionary *rowData = [self.tableDataArray objectAtIndex:row];
    cell.cellSubText = [rowData objectForKey:@"Date"];
    cell.cellText = [rowData objectForKey:@"Name"];
  cell.cellImage = [rowData objectForKey:@"Image"];
  cell.cellId = [rowData objectForKey:@"Id"];

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 
  NSString *selectedCell = [tableDataArray objectAtIndex:indexPath.row];

  DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
  detailViewController.selectedCell = selectedCell;
  [self.navigationController pushViewController:detailViewController animated:YES];
  [detailViewController release];
  detailViewController = nil;
}

- (void)dealloc { 
    [super dealloc];
}

@end

CustomCell.h:

#import "ABTableViewCell.h"

@interface CustomCell : ABTableViewCell {
  NSString * cellSubText;
  NSString * cellText;
  NSString * cellImage;
  NSString * cellIcon;
  NSString * cellId;
}

@property (nonatomic, copy) NSString *cellSubText;
@property (nonatomic, copy) NSString *cellText;
@property (nonatomic, copy) NSString *cellImage;
@property (nonatomic, copy) NSString *cellIcon;
@property (nonatomic, copy) NSString *cellId;

@end

CustomCell.m:

#import "CustomCell.h"

@implementation CustomCell

@synthesize cellSubText, cellText, cellImage, cellIcon, cellId;

static UIFont *celSubFont = nil;
static UIFont *cellFont = nil;

+ (void)initialize {
    if(self == [CustomCell class]) {
        cellSubFont = [[UIFont systemFontOfSize:13] retain];
        cellFont = [[UIFont boldSystemFontOfSize:17] retain];
    }
}

- (void)setFirstText:(NSString *)s {
    [cellSubText release];
    cellSubText = [s copy];
    [self setNeedsDisplay]; 
}

- (void)setLastText:(NSString *)s {
    [cellText release];
    cellText = [s copy];
    [self setNeedsDisplay]; 
}

- (void)drawContentView:(CGRect)r {
    CGContextRef context = UIGraphicsGetCurrentContext();

    UIColor *backgroundColour = [UIColor whiteColor];
    UIColor *categoryColour = [UIColor grayColor];
    UIColor *titleColour = [UIColor blackColor];
  if(self.highlighted || self.selected) {
    backgroundColour = [UIColor clearColor];
        categoryColour = [UIColor whiteColor];
    titleColour = [UIColor whiteColor];
    }

    [backgroundColour set];
    CGContextFillRect(context, r);

    CGPoint pCategory;
    pCategory.x = 60;
    pCategory.y = 3;

    [categoryColour set];
  [cellSubText drawAtPoint:pCategory forWidth:235 withFont:categoryFont minFontSize:13 actualFontSize:NULL lineBreakMode:UILineBreakModeTailTruncation baselineAdjustment:UIBaselineAdjustmentNone];  

  CGPoint pTitle;
    pTitle.x = 60;
    pTitle.y = 17;

  [titleColour set];
  [cellText drawAtPoint:pTitle forWidth:235 withFont:titleFont minFontSize:17 actualFontSize:NULL lineBreakMode:UILineBreakModeTailTruncation baselineAdjustment:UIBaselineAdjustmentNone];  

  //Display the image
  CGPoint pImage;
    pImage.x = 5;
    pImage.y = 5;

  NSData *imageData = [[NSData dataWithContentsOfURL:[NSURL URLWithString:cellImage]] retain];
  UIImage *image = [UIImage imageWithData:imageData];
  [image drawAtPoint:pImage];
}

- (void)dealloc {
  [cellSubText release];
  [cellText release];
  [cellIcon release];
  [cellImage release];
  [super dealloc];
}
@end

Update I think I've found the root of the problem. In the old situation, using the slow drawing process, all the cells were drown in advance and had an index. Now, only the cells that are in view are drawn (the first eight) and new ones are created on the fly when they come into view (and get a corresponding index number).

I'm not sure how to proceed from here. Is there a way to feed the data to the new cells as soon as they are created?

+1  A: 

The only thing that immediately jumped up at me was the following line:

cell = [[[CustomCell alloc] initWithFrame:CGRectZero reuseIdentifier:CustomCellIdentifier] retain];

You're creating a memory leak here and it might explain why the old cell content is sticking around.

Try changing it to:

cell = [[[CustomCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:CellIdentifier]autorelease];
Tom Irving
Thanks for pointing out the leak. But alas, it didn't solve the drawing issue.
mensch
Hmm, another thing I noticed just now is that you're downloading "imageData" synchronously in your draw method (you don't need the retain, but if you want to use it remember to release). This will cause the UI to hang and could be another reason why you're not seeing the desired results.
Tom Irving
I removed "retain" there, but I'm afraid it didn't solve anything. I think it might be related to the way I feed the data for the cells to CustomCell. In a previous version I implemented all the TableCell logic in RootViewController without problems, apart from the slow drawing.
mensch
It's not the retain that's the problem, it's the download. It's happening on the main thread, so the UI won't update until it's done, this includes the drawing.I would advice downloading the image before hand and on another thread.
Tom Irving
A: 

The problem is not with how your images are downloaded. It is that you need to call setNeedsDisplay for the variables that need to change when a new cell is loaded.

- (void)setCellImage:(NSString *)s
{
cellImage = [s copy];
[self setNeedsDisplay]; 
}

Otherwise, the new variables are not updated.

Hope this helps.

Jonah
Thanks for the reply! I think that function is already in place (setFirstText and setLastText) in CustomCell.m. Or are you saying I need to call these in cellForRowAtIndexPath in the RootViewController.m?
mensch
No, you're doing it right. I must have missed it. Sorry for the mistake.
Jonah