reloading UItableViewCell after thumbnail image downloads - ios

I have an app in which within the tableViewCell an image is downloaded and than set as a thumbnail. My problem is that the only way I can see the thumbnail image refresh is if I click on another tab, and than come back to the tab that the tableViewCell is being held. Below is the code for setting the thumbnail.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
PFObject *message = [self.messages objectAtIndex:indexPath.row];
cell.textLabel.text = [message objectForKey:#"username"];
cell.imageView.image = [UIImage imageNamed:#"Treehouse.png"];
PFFile *thumbnail = nil;
if (thumbnailArray.count > 0){
thumbnail = [[thumbnailArray objectAtIndex:indexPath.row] objectForKey:#"imageFile"];
dispatch_async(backgroundQueue, ^{
NSURL *thumbnailURL = [[NSURL alloc]initWithString:thumbnail.url];
NSData *thumbnailData = [NSData dataWithContentsOfURL:thumbnailURL];
[[cell imageView]setImage:[UIImage imageWithData:thumbnailData]];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
}
return cell;
}

The problem is.,
The cell is loaded.
The image download is fired.
Once image is downloaded, the cell is reloaded.
The image is downloaded again.
There is no caching of the image or logic to load the image
Declare a NSMutableDictionary #property (strong) NSMutableDictionary *imagesDictionary; and instantiate it self.imagesDictionary = [NSMutableDictionary dictionary];
In cell for Row at index path, check for the presence of cache before downloading it.
PFFile *thumbnail = nil;
if (thumbnailArray.count > 0)
{
//If image is present in cache load from cache
UIImage *image = [self.imagesDictionary objectForKey:indexPath];
if (image)
{
[cell.imageView setImage:image];
}
else
{
//If Image is not present fire a download
thumbnail = [[thumbnailArray objectAtIndex:indexPath.row] objectForKey:#"imageFile"];
dispatch_async(backgroundQueue, ^{
NSURL *thumbnailURL = [[NSURL alloc]initWithString:thumbnail.url];
NSData *thumbnailData = [NSData dataWithContentsOfURL:thumbnailURL];
//Cahce it to your image dictionary
[self.imagesDictionary setObject:[UIImage imageWithData:thumbnailData] forKey:indexPath];
//Reload the dictionary
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
}

I recommend you use AFNetworkings category UIImage+AFNetworking which allows you to just do [imageView setImageWithURL:(NSURL)] which manages everything in the background. You don't have to worry about loading the image, caching the image or anything like that. It will cache it for you.
https://github.com/AFNetworking/AFNetworking

The following code helped solve my problem
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
PFObject *message = [self.messages objectAtIndex:indexPath.row];
cell.textLabel.text = [message objectForKey:#"username"];
cell.imageView.image = [UIImage imageNamed:#"Treehouse.png"];
PFFile *thumbnail = nil;
if (thumbnailArray.count > 0){
thumbnail = [[thumbnailArray objectAtIndex:indexPath.row] objectForKey:#"imageFile"];
dispatch_async(backgroundQueue, ^{
NSURL *thumbnailURL = [[NSURL alloc]initWithString:thumbnail.url];
NSData *thumbnailData = [NSData dataWithContentsOfURL:thumbnailURL];
dispatch_sync(dispatch_get_main_queue(), ^{
[[cell imageView]setImage:[UIImage imageWithData:thumbnailData]];
[tableView reloadInputViews];
});
});
}
return cell;
}
The problem was that reloading the TableView was being called on a separate thread. Once I moved the setting of the image onto the main thread and calling reloadInputViews from there, my problem was fixed.

Related

How to dynamicaly load data from websites in UITableViewCell?

I'm learning iOS developing and want to make a news app that loads posts from different websites and merge it into one table. What is the code for UITableViewCell to show like the image below:
You will get many 3rd party API's which provide recent news, you can use any one of them, parse data and follow example below
This is an example using native UITableViewCell, you can use custom cell if you need your custom layout
- (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[#"description"];
}
return cell;
}

Loading images asynchronously, wrong image in cell

I have a UICollectionView in which I am loading multiple images into. From what Ive been reading, in order to match the correct image to each cell I need to subclass UIImageView and get the image there. Because every time I collectionView reloadData, some images duplicate and they are all out of order. But I am unsure how to do this and haven't found any tutorials. I am using Parse for a database.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[albumImageCell alloc]init];
}
PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
PFFile *file = [temp objectForKey:#"imageThumbnail"];
if (![cell.hasImage isEqualToString:#"YES"]) {
dispatch_async(imageQueue, ^{
NSData *data = [file getData];
if (data) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^(void){
cell.imageView.image = image;
cell.hasImage = #"YES";
});
}
});
}
return cell;
}
One way to solve this is to re-query the collection view for the cell again once you're back on the main queue. This code should work:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[albumImageCell alloc]init];
}
PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
PFFile *file = [temp objectForKey:#"imageThumbnail"];
if (![cell.hasImage isEqualToString:#"YES"]) {
dispatch_async(imageQueue, ^{
NSData *data = [file getData];
if (data) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^(void){
// cellAgain will be the actual cell at that index path, if it is visible.
// If it is not visible, cellAgain will be nil.
albumImageCell *cellAgain = [collectionView cellForItemAtIndexPath:indexPath];
cellAgain.imageView.image = image;
cellAgain.hasImage = #"YES";
});
}
});
}
return cell;
}
I made a small 'tutorial' in answer to a this question. Although the question refers to Core Data, my answer applies to any data source so you should be able to fit it around your use case.
One thing you want to watch out for is the inner block, when you get back onto the main queue. Given that you have no idea how long it takes to get to that point, the cell may no longer be relevant to that image (could have been reused), so you need to do a couple of additional checks...
(a) is the image still required?
if ([[tableView indexPathsForVisibleRows] containsObject:indexPath])
(b) is that cell is the correct cell for the image?
UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath];
Although this tutorial is still valid, I tend to abstract things further these days. As the viewController has to deal with thread-unsafe entities like UIKit and Core Data, it is a good idea to keep all viewController code on the main thread. Background queue abstractions should take place at a lower level, preferably in the model code.
What I ended up doing was subclassing UIImaveView and then passing the image file in cellForRow
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[albumImageCell alloc]init];
}
PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
PFFile *file = [temp objectForKey:#"imageThumbnail"];
[cell.imageView setFile:file];
return cell;
}
And then in the customImageView -
- (void) setFile:(PFFile *)file {
NSString *requestURL = file.url; // Save copy of url locally (will not change in block)
[self setUrl:file.url]; // Save copy of url on the instance
self.image = nil;
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:data];
if ([requestURL isEqualToString:self.url]) {
[self setImage:image];
[self setNeedsDisplay];
}
} else {
NSLog(#"Error on fetching file");
}
}];
}
But this gets Data every time the user scrolls to a new cell. So Im still trying to figure out how to match a particular image to a cell, without getting data every time.

Download Images for UITableViewCell using Multithread, but Cannot Load Images until Scroll the UITableView

I am trying to download images in the UITableViewCell using multi-thread. However, when I using the simulator to see the results, the images can not be loaded until I scroll the table view.
I have watched many examples and tutorials in StackOverFlow, but it still doesn't work at all. Actually, I download the images from the Flickr server and stored them into a cached dictionary. But I could still load the images for the first time, unless I scroll the table view and the images start to appear.
And here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
NSString *cellIdentifier = [NSString stringWithFormat:#"cell%ld", (long)indexPath.row];
NSDictionary *photo = self.recentPhotos [indexPath.row];
NSString *title = [photo valueForKeyPath:FLICKR_PHOTO_TITLE];
cell.textLabel.text = title;
[cell.textLabel setFont:[UIFont fontWithName:#"OpenSans" size:18]];
if ([self.cashedImages objectForKey:cellIdentifier] != nil) {
cell.imageView.image = [self.cashedImages objectForKey:cellIdentifier];
}
else
{
dispatch_queue_t queue = dispatch_queue_create("fetch photos", 0);
dispatch_async(queue, ^{
NSURL *imageURL = [FlickrFetcher URLforPhoto:photo format:FlickrPhotoFormatSquare];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
if ([tableView indexPathForCell:cell].row == indexPath.row) {
[self.cashedImages setValue:image forKey:cellIdentifier];
cell.imageView.image = [self.cashedImages valueForKey:cellIdentifier];
}
});
});
}
return cell;
}
For this task probably it is better to use a library. Usually I use the SDWebImage which is very easy to use, and handle image cashing also.
https://github.com/rs/SDWebImage
One example could be:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] ;
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
EDIT:
If you want to download all the images first and cache them you shouldn't handle this in a tableviewCell. TableViewCells will start the download, when it is created -> when they become visible.
SDWebImageView cashes the images that has been downloaded before so try to add this code in the viewDidLoad method:
for (NSURL* url in self.recentPhotos) {
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:url options:SDWebImageContinueInBackground progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//Your code if needed
}];
}
SDWebImage caches the images based on their absolute url path.
Note! GDImageLoader now (2016) has full, awesome, Swift version. It is maintained on an almost daily basis - it's really perhaps the single most critical and basically perfect library in iOS. Until Apple sensibly just include caching, it's basically a must-use library.
GDImageLoader is incredibly simple, solid - it's the best ..
https://github.com/AndreyLunevich/DLImageLoader-iOS/tree/master/DLImageLoader
No manual, no learning curve - one command. Totally fantastic.
-(UICollectionViewCell *)collectionView:(UICollectionView *)cv
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger thisRow = indexPath.row;
MeetingsCell *cell;
cell = [cv dequeueReusableCellWithReuseIdentifier:
#"CellPlayersB2" forIndexPath:indexPath];
cell.layer.shouldRasterize = YES; // TYPICALLY NEEDED ON iPhone6+
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
NSDictionary *mtg = CLOUD.players[thisRow];
NSString *hostText = mtg[#"host"]; etc...
cell.whenDescrip.text = etc ...;
NSString *imUrl = mtg[#"image"];
__weak UIBookView *loadBooky = cell.booky;
[DLImageLoader loadImageFromURL:imUrl
completed:^(NSError *error, NSData *imgData)
{
if (loadBooky == nil) return;
[loadBooky use:[UIImage imageWithData:imgData]];
}];
return cell;
}
Possibly the table doesn't redraw it's content. One fix could be to save the image in self.cashedImaged (you know that a typo, right ;) ) and then instruct the UITableView to reload the single cell using reloadRowsAtIndexPaths:
I think it will be better to add a Category on UIImageView and add a method something link this:
#interface UIImageView (Caching)
- (void)setImageWithFlickrURL:(NSURL *)url;
#end
And put your caching logic there.
And then in your cellForRow:indexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
NSURL *imageURL = [FlickrFetcher URLforPhoto:photo format:FlickrPhotoFormatSquare];
[cell.imageView setImageWithFlickrURL:imageURL];
return cell;
}
Or, you can just use caching mechanism that provides AFNetworking's UIImageView+AFNetworking
- (void)setImageWithURL:(NSURL *)url;
You need to add one line:
cell.imageView.image = [self.cashedImages valueForKey:cellIdentifier];
//add this line to make tableview redraw
[cell setNeedsLayout];

In a UICollectionView how can I preload data outside of the cellForItemAtIndexPath to use within it?

Figured out slow loading images were the behind the choppy slow effect of my collectionView.
I've been reading different Q&A's all day and various forum posts. It looks like the best way to solve this issue is to have the data pre-loaded available for the cellForItemAtIndexPath to be able to take what it needs.
I'm not sure how I can do this. I'm using parse as my backend, but sure if given a rough example I'd be able to figure out how to do it. From what I've seen so far I need a separate method to grab the data.
Here is the code:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [[self objects] count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
NSArray *people = [self objects];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[#"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
UIImage *image = [UIImage imageWithData:imageData];
[[cell contentView] setContentMode: UIViewContentModeScaleAspectFit];
[[cell imageView] setImage:image];
}];
[[cell title] setText:[current valueForKey:#"title"]];
[[cell price] setText:[NSString stringWithFormat: #"£%#", [current valueForKey:#"price"]]];
return cell;
}
So maybe the cellForItemAtIndexPath needs to call that method and take what it needs. Because the data would already be available it won't need to be loaded in the cellForItemAtIndexPath method and the cells will be populated immediately.
Please give suggestions and examples.
I was told a good way to do this would be to check for the image, if non existent provide a placeholder, if it does exist set it. Here are the changes to the above code.
Updates:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
NSArray *people = [self objects];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[#"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error)
{
if (!image) {
[[cell imageView] setImage:[UIImage imageNamed:#"placeholder.png"]];
} else {
image = [UIImage imageWithData:imageData];
//resize image
CGSize destinationSize = CGSizeMake(158,187);
UIGraphicsBeginImageContext(destinationSize);
[image drawInRect:CGRectMake(0,0,destinationSize.width, destinationSize.height)];
//New image
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Optimise image
NSData *imageDataCompressed = UIImageJPEGRepresentation(newImage, 0.4f);
// NSLog(#"Image Size %#", NSStringFromCGSize(newImage.size));//log size of image
NSLog(#"%#", [current valueForKey:#"title"]);
[[cell imageView] setImage:[UIImage imageWithData:imageDataCompressed]];
}
}
}];
[[cell title] setText:[current valueForKey:#"title"]];
[[cell price] setText:[NSString stringWithFormat: #"£%#", [current valueForKey:#"price"]]];
return cell;
}
Place holder shows fine but remains, how do I know when the image has been loaded so I can make my cells reflect that?
Thanks for your time.
Kind regards.
Update:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
NSArray *people = [self objects];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[cell.activityIndicator startAnimating];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[#"image"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:userImageFile.url, indexPath.item]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataDontLoad
timeoutInterval:6.0];
[cell.imageView setImageWithURLRequest:urlRequest
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
//resize image
CGSize destinationSize = CGSizeMake(158,187);
UIGraphicsBeginImageContext(destinationSize);
[image drawInRect:CGRectMake(0,0,destinationSize.width, destinationSize.height)];
//New image
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Optimise image
NSData *imageDataCompressed = UIImageJPEGRepresentation(newImage, 0.4f);
cell.imageView.image = [UIImage imageWithData:imageDataCompressed];
NSLog(#"Image Size %#", NSStringFromCGSize(newImage.size));//log size of image
[cell.activityIndicator stopAnimating];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Failed to download image: %#", error);
}];
return cell;
}
Latest Update:
Set up a method that gets data from parse.com and stores in an NSMutableDictionary then in a mutable array. I store the title, price and URL to image of the garment.
- (void)grabDataFromCloud
{
self.model = [NSMutableArray array];
for (PFObject *object in [self objects]) {
PFFile *imageFile = [object valueForKey:#"image"];
NSURL *url = [NSURL URLWithString:imageFile.url];
NSMutableDictionary *newObject = [NSMutableDictionary dictionaryWithDictionary:#{#"title": [object valueForKey:#"title"], #"price": [object valueForKey:#"price"], #"imageUrl": url}];
[[self model] addObject:newObject];
}
}
This gets called in my cellForItemsAtIndexPath method.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
[self grabDataFromCloud];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[cell.activityIndicator setHidden:YES];
NSMutableDictionary* d = [self.model objectAtIndex:indexPath.item];
cell.title.text = d[#"title"];
cell.price.text = [NSString stringWithFormat:#"£%#", d[#"price"]];
if (d[#"image"]) {
cell.imageView.image = d[#"image"];
} else { // if not, download it
cell.imageView.image = nil;
dispatch_queue_t backgroundQueue = dispatch_queue_create("test", 0);
dispatch_async(backgroundQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:d[#"imageUrl"]];
UIImage* img = [UIImage imageWithData:data];
d[#"image"] = img;
dispatch_async(dispatch_get_main_queue(), ^{
//causes crash Assertion failure in -[UICollectionView _endItemAnimations],
// /SourceCache/UIKit/UIKit-2935.137/UICollectionView.m:3687
// [self.collectionView reloadItemsAtIndexPaths:#[indexPath]];
});
});
}
return cell;
}
I'd suggest you to use AFNetworking's UIImageView+AFNetworking category. It will handle the placeholder etc automatically, and will do everything in a background thread, ensuring that the main thread doesn't get blocked. Specifically, this is the method you'd want to call:
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage;
It is up to you to supply a placeholder image (or nil) when the image is first needed and to start downloading it, and then to hang on to the image once it has been downloaded so that ever after that you can supply it instantly. This example is for a table view, but the principle is exactly the same; the key thing is that my data model is a bunch of NSMutableDictionary objects, and each dictionary in not only the url for the picture we are supposed to have but also a place for keeping the image once it has been downloaded:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSMutableDictionary* d = (self.model)[indexPath.row];
cell.textLabel.text = d[#"text"];
if (d[#"im"]) { // if we have a picture, supply it
cell.imageView.image = d[#"im"];
} else if (!d[#"task"]) { // if not, download it
cell.imageView.image = nil;
NSURLSessionTask* task = [self.downloader download:d[#"picurl"]
completionHandler:^(NSURL* url){
if (!url)
return;
NSData* data = [NSData dataWithContentsOfURL:url];
UIImage* im = [UIImage imageWithData:data];
d[#"im"] = im;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
}];
}
return cell;
}
I suggest a different approach.
Google for - or use the search on SO - Asynchrnous loading. Nearly every app programmer faces this issue earlier or later. Consequentially there are tons of tutorials out there.
This is one of them.
http://www.markj.net/iphone-asynchronous-table-image/
I think it is older than the UICollectionView and therfore explains it for UITableView. Both data source delegates are so close to each other that you can easily adopt the solution to your collection.
There are smarter ways of acomplishing your goal. But I think tht this way is a good starting point. You may later want to refactor the solution once you got comforatble with the approach in general.
After several days the issue was my images were far too large. I had to resize them and this instantly solved my issue.
I literally narrowed things down and checked my images to find they were not being resized by the method I thought was resizing them. This is why I need to get myself used to testing.
I learnt a lot about GCD and caching in the past few days but this issue could have been solved much earlier.

Loading images for UITableViewCell from URL (need to load them asynchronously)

I have a custom class that parses through an XML and gets URL strings for images (which I store in an array). Then I want to retrieve those strings to load images and display each in a UITableViewCell. Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
return cell;
}
Obviously, when I scroll down the table, the app loads more images, and when the images are loading, the user can't do anything. It looks like the app froze. The images I want to load are not very large (about 8kb). Maybe there is a way I could load and store them in the background? I also want to give more freedom to the user, like scrolling down and viewing cells with text but without an image. Then force the app to load the images for the cells the user is currently viewing.
Is there any way to modify my code or any other solution for proper loading of images from URL strings?
Any advice would be appreciated, thanks.
Try to load the images asynchronous, so the main thread doesn't block.
Your code would look as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = image;
});
});
return cell;
}
You can use the SDWebImage framework to load images asynchronously.
Please follow the link:
https://github.com/rs/SDWebImage
Just add the SDWebImage framework to your project and set the other linker flag '-ObjC', to use the framework.
Download link : https://github.com/rs/SDWebImage/releases
Use this for asynchronus way of loading imageview
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:image];
});
});
return cell;
}
Download this project and use asynimageveiw in your cell's imageview.. link
Use NSOperation and NSOperationQueue.A perfect solution for your problem.Use below link to see how it works.
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
Or
Use AFNetworking library It also provide a category over UIImageview that download image asynchronously.

Resources