I'm creating a UITableViewController to display the roster of a hockey team. The tableViewController makes calls to the web to get the player's stats and a small picture to display in the tableViewCell. However, when I scroll through the TableView, it isn't smooth. It's incredibly jagged. How can I make it so (if this will decrease its work load) the player's pictures don't load until they're on-screen? Here is my current code (I've subclassed UITableViewCell):
EDIT: I've edited my code to follow a comment below. The property imagesCache is actually a UIMutableDictionary (confusing, sorry). However, now I get the error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: http://app-assets3.sportngin.com/app_images/noPhoto-square.jpg?1428933774)'
*** First throw call stack:
(0x1865f6530 0x1975cc0e4 0x1864e1348 0x1000496a8 0x185f87168 0x1874d3be8 0x187425374 0x187414ecc 0x1874d694c 0x1000acf94 0x1000b7db8 0x1000b02c4 0x1000ba5d4 0x1000bc248 0x197dfd22c 0x197dfcef0)
libc++abi.dylib: terminating with uncaught exception of type NSException
Here is my code:
#import "RosterTableTableViewController.h"
#import "TFHpple.h"
#import "RosterListing.h"
#import "RosterListingCellTableViewCell.h"
@interface RosterTableTableViewController ()
@property (nonatomic, strong) NSMutableArray *rosters;
@property (nonatomic, strong) NSMutableDictionary *imagesDictionary;
@property NSMutableDictionary *imageCache;
@end
@implementation RosterTableTableViewController
- (void) loadRoster
{
NSURL *RosterURL = [NSURL URLWithString:@"http://www.lancers.com/roster/show/1502650?subseason=197271"];
NSData *RosterHTMLData = [NSData dataWithContentsOfURL:RosterURL];
TFHpple *RosterParser = [TFHpple hppleWithHTMLData:RosterHTMLData];
// Get the data
NSString *RosterNumberPathQueryString = @"//tbody[@id='rosterListingTableBodyPlayer']/tr/td[@class='number']";
NSArray *RosterNumberNodes = [RosterParser searchWithXPathQuery:RosterNumberPathQueryString];
NSString *RosterNamePathQueryString = @"//tbody[@id='rosterListingTableBodyPlayer']/tr/td[@class='name']/a";
NSArray *RosterNameNodes = [RosterParser searchWithXPathQuery:RosterNamePathQueryString];
NSString *RosterImagePathQueryString = @"//tbody[@id='rosterListingTableBodyPlayer']/tr/td[@class='photo']/a/img";
NSArray *RosterImageNodes = [RosterParser searchWithXPathQuery:RosterImagePathQueryString];
NSMutableArray *rosterItems = [[NSMutableArray alloc] initWithCapacity:0];
for (int i = 0; i < RosterNumberNodes.count; ++i) {
RosterListing *thisRosterListing = [[RosterListing alloc] init];
thisRosterListing.playerNumber = [[[RosterNumberNodes objectAtIndex:i] firstChild] content];
thisRosterListing.playerName = [[[RosterNameNodes objectAtIndex:i] firstChild] content];
thisRosterListing.playerURL = [[RosterNameNodes objectAtIndex:i] objectForKey:@"href"];
@try {
thisRosterListing.playerImageURL = [[RosterImageNodes objectAtIndex:i] objectForKey:@"src"];
}
@catch (NSException *e) {}
/*
NSLog(@"%@", thisRosterListing.playerNumber);
NSLog(@"%@", thisRosterListing.playerName);
NSLog(@"%@", thisRosterListing.playerURL);
NSLog(@"%@", thisRosterListing.playerImageURL);
*/
[rosterItems addObject:thisRosterListing];
}
self.rosters = rosterItems;
}
- (instancetype) initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
self.navigationItem.title = @"Roster";
self.imageCache = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self loadRoster];
// Load the Cell NIB file
UINib *nib = [UINib nibWithNibName:@"RosterListingCellTableViewCell" bundle:nil];
// Register this NIB, which contains the cell
[self.tableView registerNib:nib forCellReuseIdentifier:@"RosterCell"];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 54;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.rosters.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Get a new or recycled cell
RosterListingCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RosterCell" forIndexPath:indexPath];
RosterListing *thisRosterListing = [self.rosters objectAtIndex:indexPath.row];
cell.playerNumberLabel.text = thisRosterListing.playerNumber;
cell.playerNameLabel.text = thisRosterListing.playerName;
__block UIImage *image = [self.imageCache objectForKey:thisRosterListing.playerImageURL];
cell.imageView.image = image;
if(image == nil) {
//If nil it's not downloaded, so we download it,
//We MUST download in a separate thread otherwise the scroll will be really slow cause the main queue will try to download each cell as they show up and every time they show up
NSURL *imageURL = [NSURL URLWithString: thisRosterListing.playerImageURL];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:imageURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Completion Handler is executed in an async way
if([self.imageCache objectForKey:thisRosterListing.playerImageURL] == nil)
self.imageCache[thisRosterListing.playerImageURL] = image;
//We need to execute the image update in the main queue otherwise it won't work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
RosterListingCellTableViewCell *aCell = (RosterListingCellTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
aCell.imageView.image = image;
}];
}];
[dataTask resume];
}
return cell;
}