NSCache-not always cleaned? - ios

I have NSCache in which i am loading from and to- images to be displayed in collection view .
When i have to reload data to the collection view, i must clean the cache, because otherwise the collection view will find old data in there and reload it instead of the new data.
So before i reload the collection i clean my cache :
[self.myCache removeAllObjects];
Which sometimes, is not working, and i still see the old images in the collection view .
Is there another way to go all over its values and clean them ? Why is it not being cleared ?
Here is how i load and get images from and to :
-(UIImage*) imageForIndexPathRow:(NSNumber *) number
{
return [self.myCache objectForKey:[NSString stringWithFormat:#"cache:%d",[number intValue]] ];
}
-(void) setImage:(UIImage*) image forIndexPathRow:(NSNumber *) number
{
if(image)
[self.myCache setObject:image forKey:[NSString stringWithFormat:#"cache:%d",[number intValue]] ];
}
EDIT:
This is how i check the cache before load image to cell(in another thread) :
UIImage *imageToSet=nil;
UIImage *cacheImg=[self imageForIndexPathRow:[NSNumber numberWithLong:cell.tag]];
if(cacheImg==nil)
{
UIImage *image=[UIImage imageWithData:data scale:1];
imageToSet=image;
//save to cache
[self setImage:image forIndexPathRow:[NSNumber numberWithLong:cell.tag]];
}
else
imageToSet=cacheImg;

The problem comes from CollectionView cell reusing
you need to implement the else case, like this
- (UIImage *)imageForIndexPathRow:(NSNumber *) number
{
UIImage *cachedImage = [self.myCache objectForKey:[NSString stringWithFormat:#"cache:%d", [number intValue]]];
if (cachedImage) {
return cachedImage;
}
// Otherwise load it from web
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://...yourImageURL"]];
UIImage *loadedImage = [UIImage imageWithData:imageData];
// Cache it back
[self.myCache setObject:loadedImage forKey:[NSString stringWithFormat:#"cache:%d", [number intValue]]];
return loadedImage;
}
and the cellForRow should look like this (my example is on UITableView, but you can port it to CollectionView)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"YourCellId"];
cell.image = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage *image = [self imageForIndexPathRow:#(indexPath.row)];
dispatch_async(dispatch_get_main_queue(), ^{
cell.image = image;
});
});
return cell;
}

Related

Collection view images are flickering while scrolling

Im using the following code to populate a collection view cells with images.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100];
recipeImageView.image = nil;
if ([imageArray count] >0){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSData *data0 = [NSData dataWithContentsOfURL: [NSURL URLWithString:[imageArray objectAtIndex:indexPath.row]]];
UIImage *image = [UIImage imageWithData: data0];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
recipeImageView.image = image;
});
});
}
[spinnerShow stopAnimating];
return cell;
}
The problem is that, when Im scrolling the images are flickering and are flashing. Why is that so? How can I make those images to be stable without flickering?
Just a short overview, So you get your answer
UICollectionView is highly optimized, and thus only keep On-screen visible rows in memory. Now, All rows Cells are cached in Pool and are reused and not regenerated. Whenever, user scrolls the UICollectionView, it adds the just-hidden rows in Pool and reuses them for next to be visible rows.
So, now, coming to your answer
When you scroll your CollectionView, collectionView datasource method gets called again for every indexPath, coming in visible range and your image gets downloaded again
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
SOLUTION
Instantiate a instance NSMutableDictionary, outside of method.
Now in your code
#implementation ClassName{
NSMutableDictionary *cachedImage;
}
-(void)viewDidLoad(){
[super viewDidLoad];
cachedImage = [NSMutableDictionary new];
}
/*OLD CODE*/
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100];
recipeImageView.image = nil;
if ([imageArray count] >0){
//IF image is already downloaded, simply use it and don't download it.
if(cachedImage[[imageArray objectAtIndex:indexPath.row]] != nil){
recipeImageView.image = cachedImage[[imageArray objectAtIndex:indexPath.row]];
}
else{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSData *data0 = [NSData dataWithContentsOfURL: [NSURL URLWithString:[imageArray objectAtIndex:indexPath.row]]];
UIImage *image = [UIImage imageWithData: data0];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
recipeImageView.image = image;
//****SAVE YOUR DOWNLOADED IMAGE
cachedImage[[imageArray objectAtIndex:indexPath.row]] = image; //****SAVE YOUR DOWNLOADED IMAGE
});
});
}
}
/*OLD CODE*/
as per my knowledge, you are fetching image but not caching it that's why when your UICollectionViewCell gets reload, you get UIImageView's fresh instance so and this thing goes on and on in your code..
in this case, i recommend you to use SDWebImage OR AFNetworking Frameworks. because these frameworks does all the tricky stuff for you with the simple line of code (SDWebImage Framework),
NSURL* url = [NSURL URLWithString:str];
[yourImageView setBackgroundImageWithURL:url forState:UIControlStateNormal placeholderImage:kPlaceholder];

Images in UITableView changes as I scroll

I'm trying to create a feed just like the one in facebook. The problem is, the image on the succeeding rows will load the images from the initial rows and then correctly load their corresponding load. When you go to the top rows, the images previously loaded are gone. I've tried lazy loading but the problem persists. You could view the video to understand the problem better. (https://www.youtube.com/watch?v=NbgYM-1xYN4)
The images are asynchronously loaded and are fetched from our server.
Here are some Code:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [latestPosts count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary * dataDict = [latestPosts objectAtIndex:indexPath.row];
CardCell *cell = [self.feedTable dequeueReusableCellWithIdentifier:#"CardCell"];
if (cell == nil) {
cell = [[CardCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CardCell"];
}
[cell layoutSubviews];
NSURL *imageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:#"post_holder_image"]];
NSURL *postImageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:#"post_image"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
NSData *postImageData = [NSData dataWithContentsOfURL:postImageURL];
dispatch_async(dispatch_get_main_queue(), ^{
cell.brandImage.image = [UIImage imageWithData:imageData];
cell.postImage.image = [UIImage imageWithData:postImageData];
});
});
cell.brandName.text = [dataDict objectForKey:#"post_holder"];
cell.postDateTime.text = [dataDict objectForKey:#"post_datetime"];
cell.postMessage.text = [dataDict objectForKey:#"post_content"];
return cell;
}
Use below method of UITableViewCell in your custom cell and set the image property to nil.
hope it will work for you.
-(void)prepareForReuse{
[super prepareForReuse];
// Then Reset here back to default values that you want.
}
There are a few problems with the above.
As mentioned above you need to use a image as a placeholder (i.e blank white or an image of your choice) in the cell init code AND cell reuse.
You really need to cache your images, only download an image once and then store it in a dictionary. i.e.:
UIImage *cachedImage = self.images[user[#"username"]];
if (cachedImage) {
//use cached image
[button setBackgroundImage:cachedImage forState:UIControlStateNormal];
}
else {
//download image and then add it to the dictionary }
where self.images is an NSMutableDictionary. You could also look into NSCache. If you don't cache the images you will find the table is very laggy when scrolling for a large number of rows because of the image conversion from data.
However this will not completely fix the problem if you start loading a table and scroll up and down very fast the images will appear to be in the wrong places and move around until they are all loaded. Cell reuse will confuse where to put the image. Make sure you put [tableView reloadItemsAtIndexPaths:#[indexPath]]; in your download block i.e.:
NSURL *imageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:#"post_holder_image"]];
NSURL *postImageURL = [[NSURL alloc] initWithString:[dataDict objectForKey:#"post_image"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
NSData *postImageData = [NSData dataWithContentsOfURL:postImageURL];
dispatch_async(dispatch_get_main_queue(), ^{
cell.brandImage.image = [UIImage imageWithData:imageData];
cell.postImage.image = [UIImage imageWithData:postImageData];
[tableView reloadItemsAtIndexPaths:#[indexPath]];
});
});
You need to set imageview.image nil or you should set your placeholder image while reusing cells. Here is same question Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling
Other than that if you are not using parse.com api ect. you can check https://github.com/rs/SDWebImage or https://github.com/AFNetworking/AFNetworking
There are tons of answer about this topic.
[cell layoutSubviews];
cell.brandImage.image = nil;
cell.postImage.image = nil;

Images are loading in Table View in simulator but not in Device

I'm parsing images from JSON data and displaying in Table View. My code to display images in table view is -
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellidentifier=#"MyCell";
OnlineCell *cell = [tableView dequeueReusableCellWithIdentifier:cellidentifier];
if (cell == nil)
{
cell = [[OnlineCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellidentifier];
}
cell.userLabel.text = [self.allusername objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageURL = [self.alluserphoto objectAtIndex:indexPath.row];
img = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:imageURL]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.userIMage.image = img;
[indicator stopAnimating];
});
});
return cell;
}
Simulator Output -
Device OutPut -
Please tell me the how could i get images in device.
As i can see two screenshot one is Simulator and one is Device that two image common Load at both end simulator and device. So i think issue in to you image that comes from web side. Because you code is correct if there is issue in code then that not load two image as well in device.
Please test with change the image with First loaded image from your back End side and check once that have to load. put first loaded image for all response instead of goggles image that not load in device. i am dame sure issue is in image not in code.
Debugging Suggestion 1: On the device, make sure that the image is accessible. You can try opening the image in Safari. If safari is opening the image, it means that image is accessible.
Debugging Suggestion 2: If the image is accessible, try loading the image synchronously and debug if you're getting the required data (on the device):
NSURL *url = [NSURL URLWithString:self.photoImagePath]; // Is URL valid?
NSData *imageData = [NSData dataWithContentsOfURL:url]; // Is data nil?
UIImage *image = [UIImage imageWithData:imageData]; // Is image nil?
Then, try loading the image asynchronously, like this:
__weak UIImageView *weakImgageView = cell.userIMage;
imageURL = [self.alluserphoto objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:imageURL]]];
dispatch_async(dispatch_get_main_queue(), ^{
weakImgageView.image = image;
[indicator stopAnimating];
});
});
Hope this helps.
The default image is not coming properly. This is what I suspect. Please check if you are giving the name properly or not. This could be the issue.
Set default image until you fetch image asynchronously from JSON.
UIImage *img = [UIImage imageNamed:#"default.png"];
When you fetch image in asynchronously method means it will fetch depend on image size & internet speed. So you just keep a default image until it fetch completely from server
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
OnlineCell *cell = (OnlineCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray *nib = nil;
if (cell == nil)
{
nib = [[NSBundle mainBundle] loadNibNamed:#"YourCustomCellNibName" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Your code
cell.userLabel.text = [self.allusername objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//NSData *imagedata = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.alluserphoto objectAtIndex:indexPath.row]]];
NSURL* url = [NSURL URLWithString:[self.alluserphoto objectAtIndex:indexPath.row]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage* image = [[UIImage alloc] initWithData:data];
// do whatever you want with image
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
OnlineCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
cell.userIMage.image = image;
});
}
}
}];
});
}

TableView scrolling stutters after adding images from remote URL

I am building an iOS app that adds XML items to a TableView (among other things, of course). I would like to display the article's thumbnail in the TableView cell or a default placeholder if the article's thumbnail field is empty.
The code below adds the thumbnail from the article but then causes scrolling to stutter quite a bit. If I only use the placeholder image for each cell, everything is fine. I am guessing I am probably not using the most ideal method to add the thumbnail image, not sure if this is causing the problem.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"idCellNewsTitle"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"idCellNewsTitle"];
}
NSDictionary *dict = [self.arrNewsData objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"holder-small.jpg"];
cell.textLabel.text = [dict objectForKey:#"title"];
cell.detailTextLabel.text = [dict objectForKey:#"pubDate"];
if ([dict objectForKey:#"thumbnail"] != nil) {
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString: [dict objectForKey:#"thumbnail"]]];
if (imgData) {
UIImage *image = [UIImage imageWithData:imgData];
if (image) {
cell.imageView.image = image;
}
}
}
return cell;
}
Coding in the latest XCode, testing in simulator and on a newer iPod - results are the same with both. No warnings or errors during the running of the app, CPU spikes at 1% when scrolling and memory stays around 16 MB.
UPDATE - Video
Here is a video demonstrating this issue for any future noobs to compare with -
First example is with a placeholder, second is without.
Video on YouTube
replace your code in cellForRowAtIndexPath:
after this line if ([dict objectForKey:#"thumbnail"] != nil)
That way you load each image in the background and as soon as its loaded the corresponding cell is updated on the mainThread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSData *imgData = NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString: [dict objectForKey:#"thumbnail"]]];
if (imgData) {
UIImage *image = [UIImage imageWithData:imgData];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
UIImage *image = [UIImage imageWithData:imgData];
if (image) {
cell.imageView.image = image;
}
});
});
LazyTableImages Reference
Fetch images using GCD
// Fetch using GCD
dispatch_queue_t downloadThumbnailQueue = dispatch_queue_create("Get Photo Thumbnail", NULL);
dispatch_async(downloadThumbnailQueue, ^{
UIImage *image = [self getTopPlacePhotoThumbnail:photo];
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *cellToUpdate = [self.tableView cellForRowAtIndexPath:indexPath]; // create a copy of the cell to avoid keeping a strong pointer to "cell" since that one may have been reused by the time the block is ready to update it.
if (cellToUpdate != nil) {
[cellToUpdate.imageView setImage:image];
[cellToUpdate 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.

Resources