Asynchronous downloading of images for UITableView with GCD
The problem here is that your image-fetching blocks are holding references to the tableview cells. When the download completes, it sets the imageView.image
property, even if you have recycled the cell to display a different row.
You'll need your download completion block to test whether the image is still relevant to the cell before setting the image.
It's also worth noting that you're not storing the images anywhere other than in the cell, so you'll be downloading them again each time you scroll a row onscreen. You probably want to cache them somewhere and look for locally cached images before starting a download.
Edit: here's a simple way to test, using the cell's tag
property:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.tag = indexPath.row;
NSDictionary *parsedData = self.loader.parsedData[indexPath.row];
if (parsedData)
{
cell.imageView.image = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
if (cell.tag == indexPath.row) {
cell.imageView.image = image;
[cell setNeedsLayout];
}
});
}
});
cell.textLabel.text = parsedData[@"id"];
}
return cell;
}
Asynchronous UITableViewCell Image Loading Using GCD
You should call setNeedsLayout
on the cell after setting the thumbnail.
From the Apple Doc:
"Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance."
GCD UITableView asynchronous load images, wrong cells are loaded until new image download
Rather than capturing the cell you need to capture the index path, then get the cell back using:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
That way, if the cell is now off screen you'll get nil back and the image won't be set on the wrong cell.
The other thing you need to add after your dispatch_async()
is a cell.imageView.image=somePlaceholderImage
.
E.g.:
if (![[NSFileManager defaultManager] fileExistsAtPath:[path stringByAppendingPathComponent:@"image.png"]])
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSString *url=[pat stringByAppendingPathComponent:@"comments.txt"];
NSString *u=[NSString stringWithContentsOfFile:url encoding:NSUTF8StringEncoding error:nil];
NSURL *imageURL=[NSURL URLWithString:u];
NSData *image=[NSData dataWithContentsOfURL:imageURL];
[image writeToFile:[pat stringByAppendingPathComponent:@"image.png"] atomically:YES];
dispatch_sync(dispatch_get_main_queue(), ^{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image=[UIImage imageWithContentsOfFile:[pat stringByAppendingPathComponent:@"image.png"]];
[cell setNeedsLayout];
NSLog(@"Download");
});
});
cell.imageView.image=[UIImage imageNamed:@"placeholder"];
}
else
{
NSLog(@"cache");
cell.imageView.image=[UIImage imageWithContentsOfFile:[pat stringByAppendingPathComponent:@"image.png"]];
}
Easy load images asynchronously in a UITableView using GCD
Use NSCache to hold your downloaded images instead of NSDictionary. This will manage itself in low memory situations and remove items that haven't been accessed for a while. In that situation, they will be loaded from the URL again if needed.
Related Topics
Zooming Mkmapview to Fit Annotation Pins
Uibutton Custom Font Vertical Alignment
Always Pass Weak Reference of Self into Block in Arc
How to Compare Two Uiimage Objects
Swift Native Functions to Have Numbers as Hex Strings
How to Hide the Status Bar in a Swift iOS App
iOS 7 Status Bar with Phonegap
How to Animate the Textcolor Property of an Uilabel
How to Implement Re-Ordering of Coredata Records
How to See Saved Nsuserdefaults
Event Handling For iOS - How Hittest:Withevent: and Pointinside:Withevent: Are Related
Xcode, Where to Assign the Segue Identifier
How to Upload Images to a Server in iOS With Swift
Xcode 9 Error: "Iphone Has Denied the Launch Request"
How to Determine Uiwebview Height Based on Content, Within a Variable Height Uitableview