UITableView with complex cells is slow and laggy

ios, objective-c, uitableview

Solution

I would move as much of the logic out of `cellForRowAtIndexPath:` as you can, it needs to be very light-weight to get good scrolling performance. You're doing a lot of work on the main thread, and I would do a lot more of this work when you get your model objects back from Parse (if you could post `viewDidLoad` I can give you more specific help) and update the table view when these calls are done:

- `[UIImage imageWithData:data]`

- anything to do with `NSDateFormatter`

- `CLLocation`'s `initWithLatitude:longitude:`

- creating the rating stars image

None of these depend on the state of the table view, so they can be effectively precomputed and cached in a model object. If you simply scroll up and down the table, you're doing allo f the same work over and over, killing your performance.

Updated for the questioner's newest code:

I won't include all of your functionality here but this should give you an idea:

// create a single shared formatter instead of one per object
NSDateFormatter *dateFormatter = [NSDateFormatter dateFormatFromTemplate:@"dd/MM" options:0 locale:[NSLocale currentLocale]];
NSDateFormatter *timeFormatter = [NSDateFormatter dateFormatFromTemplate:@"j:mm" options:0 locale:[NSLocale currentLocale]];

[hours findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    if (!error) {
        self.objectsNumber = objects.count;

        for (SomeObject *modelObj in objects) {
             // if you can add properties to your model object directly, do that
             // otherwise write a category on the Parse object to add the ones you need
             modelObj.dateString = [NSString stringWithFormat:@"%@ %@ %@ %@", modelObj.dal, [self.dateFormatter stringFromDate:[modelObj objectForKey:@"startDate"]], modelObj.ore, [self.timeFormatter stringFromDate:[modelObj objectForKey:@"endDate"]]];

             // create your locations, images, etc in here too
        }

        self.hH = [[NSArray alloc] initWithArray:objects];
    }
}];]

Then in `cellForRowAtIndexPath:`, take the precomputed properties and simply assign them to the appropriate labels, image views, etc.

It would be even better to do most of this processing off the main thread via GCD, but that is most likely out of scope for this question. See Using GCD and Blocks Effectively for more information. Just remember do only interact with UIKit from the main thread!

Problem

I've almost finished my app and everything seems to work but the main view. It's an `UIViewController` with an embedded `UITableView`. I'm using Parse as the backend, and I get an array of the objects I need in my `viewDidLoad` method. Each cell contains some data that I'm fetching in the `tableView:cellForRowAtIndexPath` and I'm afraid that this is the reason why my table view is so laggy, but I don't know how to fetch the data I need for each object in my array without having the `indexPath.row` number. I've already made each cell element "opaque" as suggested in other answers. This is my code, any help would be greatly appreciated: ``` - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"cellHT"; CellHT *cell = (CellHT *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (!cell) { cell = [[CellHT alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // self.hH is an NSArray containing all the objects NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; cell.lblTitle.text = [self.hH[indexPath.row] objectForKey:@"title"]; cell.lblVenueName.text = [self.hH[indexPath.row] objectForKey:@"venueName"]; cell.lblDistance.text = NSLocalizedString(@"Distance from you", nil); self.geo = [self.hH[indexPath.row] objectForKey:@"coordinates"]; // the formatters are initialized in the viewDidLoad: method self.formatData = [NSDateFormatter dateFormatFromTemplate:@"dd/MM" options:0 locale:[NSLocale currentLocale]]; [self.formatterData setDateFormat:self.formatData]; self.formatOra = [NSDateFormatter dateFormatFromTemplate:@"j:mm" options:0 locale:[NSLocale currentLocale]]; [self.formatterOra setDateFormat:self.formatOra]; self.dal = NSLocalizedString(@"from", nil); self.ore = NSLocalizedString(@"at", nil); CLLocation *vLoc = [[CLLocation alloc] initWithLatitude:self.geo.latitude longitude:self.geo.longitude]; CLLocation *user = [[CLLocation alloc] initWithLatitude:self.userGeo.latitude longitude:self.userGeo.longitude]; CLLocationDistance distance = [user distanceFromLocation:venueLoc]; if ([[prefs objectForKey:@"unit"] isEqualToString:@"km"]) { cell.lblDist.text = [NSString stringWithFormat:@"%.1f Km", distance /1000]; } else { cell.lblDist.text = [NSString stringWithFormat:@"%.1f Miles", distance /1609]; } // compare the object's starting date with the current date to set some images in the cell NSComparisonResult startCompare = [[self.hH[indexPath.row] objectForKey:@"startDate"] compare: [NSDate date]]; if (startCompare == NSOrderedDescending) { cell.quad.image = [UIImage imageNamed:@"no_HT"]; cell.lblStartTime.textColor = [UIColor redColor]; } else { cell.quad.image = [UIImage imageNamed:@"yes_HT"]; cell.lblStartTime.textColor = [UIColor colorWithRed:104.0/255.0 green:166.0/255.0 blue:66.0/255.0 alpha:1.0]; } NSString *dataInizio = [NSString stringWithFormat:@"%@ %@ %@ %@", self.dal, [self.formatterData stringFromDate:[self.hH[indexPath.row] objectForKey:@"startDate"]], self.ore, [self.formatterOra stringFromDate:[self.hH[indexPath.row] objectForKey:@"endDate"]]]; cell.lblStartTime.text = dataInizio; PFObject *cat = [self.hH[indexPath.row] objectForKey:@"catParent"]; NSString *languageCode = [[NSLocale preferredLanguages] objectAtIndex:0]; if ([languageCode isEqualToString:@"it"]) { cell.lblCategory.text = [cat objectForKey:@"nome_it"]; } else if ([languageCode isEqualToString:@"es"]) { cell.lblCategory.text = [cat objectForKey:@"nome_es"]; } else { cell.lblCategory.text = [cat objectForKey:@"nome_en"]; } //getting the image data from the Parse PFFile PFFile *theImage = [self.hH[indexPath.row] objectForKey:@"photo"]; [theImage getDataInBackgroundWithBlock:^(NSData *data, NSError *error) { if (!error) { cell.cellImageView.image = [UIImage imageWithData:data]; } }]; //getting the cell object's owner and his profile PFUser *usr = [self.hH[indexPath.row] objectForKey:@"parent"]; PFQuery *prof = [PFQuery queryWithClassName:@"Profile"]; prof.cachePolicy = kPFCachePolicyCacheThenNetwork; [prof whereKey:@"parent" equalTo:usr]; [prof getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) { if (!error) { //getting the object's rating and the number of votes PFQuery *rateQuery = [PFQuery queryWithClassName:@"Rating"]; [rateQuery whereKey:@"parent" equalTo:object]; [rateQuery getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) { if (!error) { float vote = [[object objectForKey:@"rate"] floatValue]; float temp = ((vote * 2) + 0.5); int tempvote = (int)temp; float roundedVote = (float)tempvote / 2; // drawing the stars number, depending on the rating obtained UIImage *starsImage = [UIImage imageNamed:@"stars"]; UIGraphicsBeginImageContextWithOptions(cell.imgVoto.frame.size, NO, 0); CGPoint starPoint = (CGPoint) { .y = (cell.imgVoto.frame.size.height * (2 * roundedVote + 1)) - (starsImage.size.height) }; [starsImage drawAtPoint:starPoint]; cell.imgVoto.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); cell.lblVoto.text = [NSString stringWithFormat:@"(%d)", [[object objectForKey:@"voters"] intValue]]; } }]; } }]; return cell; } ``` EDIT: this is the cell code: ``` + (void)initialize { if (self != [HH class]) { return; } } -(id)initWithCoder:(NSCoder *)aDecoder { if ( !(self = [super initWithCoder:aDecoder]) ) return nil; self.cellImageView.image = [UIImage imageNamed:@"icona_foto"]; self.cellImageView.contentMode = UIViewContentModeScaleToFill; self.formatterData = [[NSDateFormatter alloc] init]; self.formatData = [[NSString alloc] init]; self.formatterOra = [[NSDateFormatter alloc] init]; self.formatOra = [[NSString alloc] init]; self.formatData = [NSDateFormatter dateFormatFromTemplate:@"dd/MM" options:0 locale:[NSLocale currentLocale]]; [self.formatterData setDateFormat:self.formatData]; self.formatOra = [NSDateFormatter dateFormatFromTemplate:@"j:mm" options:0 locale:[NSLocale currentLocale]]; [self.formatterOra setDateFormat:self.formatOra]; self.lblVoto.text = @"(0)"; return self; } ``` SECOND EDIT: this is the code in the `viewDidLoad` method: ``` PFQuery *hours = [PFQuery queryWithClassName:@"HH"]; hours.cachePolicy = kPFCachePolicyCacheThenNetwork; // here I'm making lots of query constraints that I'll not include [hours findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) { if (!error) { self.objectsNumber = objects.count; self.hH = [[NSArray alloc] initWithArray:objects]; } }]; [self.tableView reloadData]; } ```

Original source