2

I'm loading some data from a SQLite database to be displayed in a UITableView. To make sure the user is not blocked and the table is displayed quickly I'm creating a queue and adding the database operation to be executed in the background.

The add is good and works fine but for some reason randomly some of the rows are not updated with the data!

I know the data is set correctly as when I click on the row the row is updated with my data but I can't seem to find a way to update the UI! I've tried everything including using setNeedDisplay and so on but nothing works!

Here is my code:

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

    UITableViewCell* cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.textLabel.textAlignment = UITextAlignmentRight;
        cell.textLabel.numberOfLines = 0;
        cell.textLabel.font = [UIFont systemFontOfSize:14];
    }

    cell.accessoryType = UITableViewCellAccessoryNone;
    cell.textLabel.text = @"Loading ...";
    cell.tag = indexPath.row;

    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self 
                                                                     selector:@selector(setCellData:) 
                                                                       object:cell];

    [queue addOperation:op];
    [op release];

    return cell;
}


- (void) setCellData:(UITableViewCell *)cell{

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    sqlite3_stmt            *statement2;

    NSString *sqlStr = [NSString stringWithFormat:@"SELECT * FROM poemdata WHERE catid='%d' GROUP BY poemid LIMIT 1 OFFSET %d",catId,cell.tag];
//  NSLog(@"%@",sqlStr);
    sqlite3_prepare_v2(database, [sqlStr cStringUsingEncoding:NSASCIIStringEncoding], -1, &statement2, nil);

    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    if(sqlite3_step(statement2) == SQLITE_ROW){
        cell.textLabel.text = [[NSString alloc] initWithUTF8String: (char *)sqlite3_column_text(statement2, 3)];
        cell.tag = sqlite3_column_int(statement2, 6);
    }else{
        cell.textLabel.text = @"Error";
    }
    sqlite3_finalize(statement2);

    [pool release];
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
Amir
  • 33
  • 5

2 Answers2

4

Your logic is flawed and I think you will experience terrible performance as well as the problems you are already experiencing. It would be better to launch an overall Operation to load all your data or at least a portion of your data and then asking the table to reload when it finishes.

The way you are doing it now is that every cell is hitting the database and selecting one row and not caching this data anywhere (meaning if it scrolls out of view and then back in again it will have to hit the database again). In addition there is no drawing code called on this cell when the operation finishes (which is why it doesn't show up until you click it causing a draw call).

You should load all of this data into an array of custom objects. Once loaded you should reload the table and use the newly filled array to set your properties in the cell.

theChrisKent
  • 15,029
  • 3
  • 61
  • 62
  • Well the issue is the size, this database is huge so I can't really load all my rows and the logic to load part of it is complex at best. So I need to do something like what I've done. The question is what to do to get the cells be updated on screen. On the drawing part of your answer adding [cell setNeedDisplay] wouldn't help. The issue here is timing, the cell is displayed before the data is set and is never refreshed for some reason. – Amir Mar 17 '11 at 18:31
  • 1
    @Amir if the issue is size (which is not an uncommon issue) you will want to implement the same type of paging behavior many apps who face similar issues (whether from a local database or a network connection) do. You should load a large initial set (say 100-200 records depending on the complexity of your cell layout) and then when the user gets to the bottom of the list load another 25-50 and release the first 25-50 keeping a rolling window in memory. This isn't nearly as complex as trying to get your posted code working. – theChrisKent Mar 17 '11 at 18:42
  • Thanks Chris, I'll consider looking into changing my logic :) That said I still like to know why this is not working! – Amir Mar 17 '11 at 18:44
1

You're updating your user interface from your background thread, which is wrong. All user interface work must be done on the main thread. Try moving all interaction with cell to the main thread, passing the data from the background thread in an NSDictionary.

See performSelectorOnMainThread:withObject:waitUntilDone:. Also read Threads and Your User Interface in Apple's Threading Programming Guide.

@theChrisKent made good points about performance, though this threading issue is more likely the cause of your erratic behavior.

Steve Madsen
  • 13,465
  • 4
  • 49
  • 67