ios - Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling -
i've written 2 ways async load pictures inside uitableview cell. in both cases image load fine when i'll scroll table images change few times until scroll end , image go right image. have no idea why happening.
#define kbgqueue dispatch_get_global_queue(dispatch_queue_priority_default, 0) - (void)viewdidload { [super viewdidload]; dispatch_async(kbgqueue, ^{ nsdata* data = [nsdata datawithcontentsofurl: [nsurl urlwithstring: @"http://myurl.com/getmovies.php"]]; [self performselectoronmainthread:@selector(fetcheddata:) withobject:data waituntildone:yes]; }); } -(void)fetcheddata:(nsdata *)data { nserror* error; myjson = [nsjsonserialization jsonobjectwithdata:data options:kniloptions error:&error]; [_mytableview reloaddata]; } - (nsinteger)numberofsectionsintableview:(uitableview *)tableview { // return number of sections. return 1; } - (nsinteger)tableview:(uitableview *)tableview numberofrowsinsection:(nsinteger)section{ // return number of rows in section. // number of items in array (the 1 holds list) nslog(@"myjson count: %d",[myjson count]); return [myjson count]; } - (uitableviewcell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath{ mycell *cell = [tableview dequeuereusablecellwithidentifier:@"cell"]; if (cell == nil) { cell = [[mycell alloc] initwithstyle:uitableviewcellstyledefault reuseidentifier:@"cell"]; } dispatch_async(kbgqueue, ^{ nsdata *imgdata = [nsdata datawithcontentsofurl:[nsurl urlwithstring:[nsstring stringwithformat:@"http://myurl.com/%@.jpg",[[myjson objectatindex:indexpath.row] objectforkey:@"movieid"]]]]; dispatch_async(dispatch_get_main_queue(), ^{ cell.poster.image = [uiimage imagewithdata:imgdata]; }); }); return cell; }
... ...
- (uitableviewcell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath{ mycell *cell = [tableview dequeuereusablecellwithidentifier:@"cell"]; if (cell == nil) { cell = [[mycell alloc] initwithstyle:uitableviewcellstyledefault reuseidentifier:@"cell"]; } nsurl* url = [nsurl urlwithstring:[nsstring stringwithformat:@"http://myurl.com/%@.jpg",[[myjson objectatindex:indexpath.row] objectforkey:@"movieid"]]]; nsurlrequest* request = [nsurlrequest requestwithurl:url]; [nsurlconnection sendasynchronousrequest:request queue:[nsoperationqueue mainqueue] completionhandler:^(nsurlresponse * response, nsdata * data, nserror * error) { if (!error){ cell.poster.image = [uiimage imagewithdata:data]; // whatever want image } }]; return cell; }
assuming you're looking quick tactical fix, need make sure cell image initialized , cell's row still visible, e.g:
- (uitableviewcell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath { mycell *cell = [tableview dequeuereusablecellwithidentifier:@"cell" forindexpath:indexpath]; cell.poster.image = nil; // or cell.poster.image = [uiimage imagenamed:@"placeholder.png"]; nsurl *url = [nsurl urlwithstring:[nsstring stringwithformat:@"http://myurl.com/%@.jpg", self.myjson[indexpath.row][@"movieid"]]]; nsurlsessiontask *task = [[nsurlsession sharedsession] datataskwithurl:url completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { if (data) { uiimage *image = [uiimage imagewithdata:data]; if (image) { dispatch_async(dispatch_get_main_queue(), ^{ mycell *updatecell = (id)[tableview cellforrowatindexpath:indexpath]; if (updatecell) updatecell.poster.image = image; }); } } }]; [task resume]; return cell; }
the above code addresses few problems stemming fact cell reused:
you're not initializing cell image before initiating background request (meaning last image dequeued cell still visible while new image downloading). make sure
nil
image
property of image views or else you'll see flickering of images.a more subtle issue on slow network, asynchronous request might not finish before cell scrolls off screen. can use
uitableview
methodcellforrowatindexpath:
(not confused nameduitableviewdatasource
methodtableview:cellforrowatindexpath:
) see if cell row still visible. method returnnil
if cell not visible.the issue cell has scrolled off time async method has completed, and, worse, cell has been reused row of table. checking see if row still visible, you'll ensure don't accidentally update image image row has since scrolled off screen.
somewhat unrelated question @ hand, still felt compelled update leverage modern conventions , api, notably:
use
nsurlsession
rather dispatching-[nsdata contentsofurl:]
background queue;use
dequeuereusablecellwithidentifier:forindexpath:
ratherdequeuereusablecellwithidentifier:
(but make sure use cell prototype or register class or nib identifier); andi used class name conforms cocoa naming conventions (i.e. start uppercase letter).
even these corrections, there issues:
the above code not caching downloaded images. means if scroll image off screen , on screen, app may try retrieve image again. perhaps you'll lucky enough server response headers permit transparent caching offered
nsurlsession
,nsurlcache
, if not, you'll making unnecessary server requests , offering slower ux.we're not canceling requests cells scroll off screen. thus, if rapidly scroll 100th row, image row backlogged behind requests previous 99 rows aren't visible anymore. want make sure prioritize requests visible cells best ux.
the simplest fix addresses these issues use uiimageview
category, such provided sdwebimage or afnetworking. if want, can write own code deal above issues, it's lot of work, , above uiimageview
categories have done you.
Comments
Post a Comment