ios tableview images not freeing memory - ios

Simple scenario: table view controller, with 5.000 rows.
I have 5000 images name from 0.png to 4999.png.
Here is my cellForRowAtIndexPath: method :
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *string = [NSString stringWithFormat:#"%d",indexPath.row];
UIImage *img = [UIImage imageNamed:string];
if (img) {
cell.imageView.image = img;
} else {
cell.imageView.image = [UIImage imageNamed:#"NoPhotoDefault"];
}
return cell;
I've analized the behaviour whit instruments, and it indicates that real memory usage is rising fast at scrolling and it never goes down.
It start from about 8 MB and it goes up to 200 MB when reaching last row.
What am i missing here?
Thanks
P.S. Proj base sdk is 5.0, and using ARC.
Later edit, when sending the app in background, memory is freed.

UIImage caches the images when you use the method imageNamed:. It does to avoid reloading the same image when it is used multiple times.
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.
If you want to avoid caching the images, use imageWithContentsOfFile: instead.
This method does not cache the image object.

Related

UITableView thumbnail image performance

My image data is being loaded from core data into a UITableView cell. These images are automatically scaled by the OS (as far as I know) in cellForRowAtIndexPath:indexPath:. Not surprisingly, this causes a lot of lag while scrolling through the table view.
Similarly, I have a UICollectionViewController that loads all the same images into a collection view similar to the iOS Photos app and again, the images are scaled in cellForItemAtIndexPath:indexPath. Scrolling is laggy, but it also takes a very long time for the VC to load.
What is the best way to optimize performance in this scenario?
I've done some research and have come up with a couple possible solutions:
Initialize a "thumbnailArray" in viewDidLoad:animated:, scaling all the images I need before the table/collection view is loaded, then use this new array as the data source for the views. I figure this will solve the scrolling issue, but not the collection view loading issue.
Create new properties for thumbnail data in my image wrapper class. This data would be created when the image wrapper object is created (i.e. when the user adds an image) and saved in core data. I think this would be preferred over option #1.
Is option two the best way to go, or is there a better solution I am unaware of?
Here are my cellForRowAtIndexPath:indexPath and cellForItemAtIndexPath:indexPath: methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CMAEntryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"entriesCell" forIndexPath:indexPath];
CMAEntry *entry = [self.entries objectAtIndex:indexPath.item];
cell.speciesLabel.text = [entry.fishSpecies name];
cell.dateLabel.text = [self.dateFormatter stringFromDate:entry.date];
if (entry.location)
cell.locationLabel.text = [entry locationAsString];
else
cell.locationLabel.text = #"No Location";
if ([entry.images count] > 0)
cell.thumbImage.image = [[entry.images objectAtIndex:0] dataAsUIImage];
else
cell.thumbImage.image = [UIImage imageNamed:#"no_image.png"];
if (indexPath.item % 2 == 0)
[cell setBackgroundColor:CELL_COLOR_DARK];
else
[cell setBackgroundColor:CELL_COLOR_LIGHT];
return cell;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"thumbnailCell" forIndexPath:indexPath];
UIImageView *imageView = (UIImageView *)[cell viewWithTag:100];
[imageView setImage:[[self.imagesArray objectAtIndex:indexPath.item] dataAsUIImage]];
return cell;
}
Thanks in advance!
Cohen
Thanks for your input, guys, but this is the solution that worked really well for me. The only thing I don't like is that now I store an NSData instance of the full image, and two different sized thumbnail images in core data; however, that doesn't seem to be a problem.
What I did was add a couple attributes to my NSManagedObject subclass to store the thumbnail images. The thumbnail data is initialized when the image is selected by the user, then saved in core data along with the original image.
Then, I load the thumbnails into a collection asynchronously in the view controllers I need them.
Works great. Gets rid of all issues I was experiencing.
1 I think the best approach is to load images in a background thread so the tableview loads quickly.
2 Also you can use coredata feature of batchsize to load only necessary data.
3 Perhaps using some type of cache in memory for the images may help
In one of my project i faced same kind of issue and i solved it by using AsyncImageView for loading the thumbimage for my tableview cell.It will load the image asynchronously.
#property (nonatomic,strong)AsyncImageView * thumbImageView;
self.thumbImageView =[[AsyncImageView alloc] init];
cell.thumbImageView.imageURL =[NSURL fileURLWithPath:UrlString];

UITableView scrolling is not smooth

I have the smooth scrolling issue at my UITableView with UITableViewCell which contains UIImageView. Similar issues could be found all over the StrackOverflow but none of the proposed solutions helped me to completely get rid of the lag.
My case is quite common:
images are stored at application storage (in my sample at app bundle)
images could have different size (500x500, 1000x1000, 1500x1500)
I need to display those images in UITableView where UIImageView size is 120x120 (retina)
I have followed multiple optimization tips and managed to optimize scrolling a lot.
Unfortunately it is still not perfect. This is my scenario:
first I moved all the image loading/processing/resizing logic to the background thread
UITableViewCell reuse is enabled
once UITableViewCell is in view I clear old values (settings to null) and start background thread to load the image
at this point we are in background thread and I'm adding 500 ms delay to avoid settings new image to often (in case we are scrolling fast) (see below explanation)
if UIImage exists at static image cache (regular dictionary with UIImage instances) - fetch that one and go to the step 9.
if not - load new image from bundle (imageWithName) using url to app bundle (in real world scenario images will be stored to application storage, not bundle)
once image is loaded resize it to 120x120 using graphics context
save resized image to the static image cache
at this point we have instance to UIImage and process is in the background thread. From here we move back to UI Thread with the given image
if data context was cleared (for example UITableViewCell disappeared or was reused to display another image) we skip processing of the currently available image.
if data context is the same - assign UIImage to UIImageView with an alpha animation (UIView.Animate)
once UITableViewCell is out of view - clear the data context
Originally before starting new background thread to fetch the image here (step 1) was UIImage cache check without background thread. In this case if we have the image in the cache we assign it instantly and this introduces a great lag during fast scrolling (we assign images to often as long as we fetch them instantly). Those lines are commented at my example attached below.
There are still two issues:
at some point during scrolling I still have a small lag (at the
moment when I'm assign new UIImage to UIImageView.
(this one is more noticeable) when you tap on item and go back from details there is a lag right before back navigation animation is finished.
Any suggest how to deal with those two issues or how to optimize my scenario are appreciated
Please take into account that sample written in Xamarin but I don't believe that Xamarin is the cause of the problem as long as I have the same issue for the app written in ObjectiveC as well.
Smooth Scrolling Test App
Did you every tried to populate your TableView with only one 120x120 Image which is saved in your Bundle? This way you can check, if the problem occurs of your Image rendering
Instead of resizing all your images to 120x120 and save them in cache, I would recommend creating and using a thumbnail of all your images. You are somehow already doing this, but you are doing this couple of times (everytime you are scrolling or if your cache is full).
In our last project we had a UICollectionView with book covers. Most of the covers were between 400-800kb big and the feeling while scrolling was really bad. So we created a thumbnail for each image (thumbails about 40-50kb) and used the thumbnails instead of real covers. Works like a charm! I attached the thumbnail creation function
- (BOOL) createThumbnailForImageAtFilePath:(NSString *)sourcePath withName:(NSString *)name {
UIImage* sourceImage = [UIImage imageWithContentsOfFile:sourcePath];
if (!sourceImage) {
//...
return NO;
}
CGSize thumbnailSize = CGSizeMake(128,198);
float imgAspectRatio = sourceImage.size.height / sourceImage.size.width;
float thumbnailAspectRatio = thumbnailSize.height/thumbnailSize.width;
CGSize scaledSize = thumbnailSize;
if(imgAspectRatio >= thumbnailAspectRatio){
//image is higher than thumbnail
scaledSize.width = scaledSize.height * thumbnailSize.width / thumbnailSize.height;
}
else{
//image is broader than thumbnail
scaledSize.height = scaledSize.width * imgAspectRatio;
}
UIGraphicsBeginImageContextWithOptions( scaledSize, NO, 0.0 );
CGRect scaledImageRect = CGRectMake( 0.0, 0.0, scaledSize.width, scaledSize.height );
[sourceImage drawInRect:scaledImageRect];
UIImage* destImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSString* thumbnailFilePath = [[self SOMEDIRECTORY] stringByAppendingPathComponent:name];
BOOL success = [UIImageJPEGRepresentation(destImage, 0.9) writeToFile:thumbnailFilePath atomically:NO];
return success;
}
Try facebook's Async Display library.
https://github.com/facebook/AsyncDisplayKit
Really easy to use.. from their guide: http://asyncdisplaykit.org/guide/
_imageNode = [[ASImageNode alloc] init];
_imageNode.backgroundColor = [UIColor lightGrayColor];
_imageNode.image = [UIImage imageNamed:#"hello"];
_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f);
[self.view addSubview:_imageNode.view];
This decodes the image on a background thread.
I'm not sure if it's easy to use iOS libraries on Xamarin but if it's easy, give this a shot.
I sub-class Paul Hegarty's CoreDataTableViewController and employ thumbnails of my photos in the CoreDataTableView.
Look for the examples in Lecture 14 titled FlickrFetcher and Photomania. You will also need to download the CoreDataTableViewController at that same link.
Make a CoreData Entity with an appropriate title and define whatever attributes (data variables) you want. You will need to define two "Transformable" attributes, one for the photo and one for the thumbnail.
Then load your thumbnail in the CoreDataTableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *exceptions = [NSArray arrayWithObjects:#"SCR", #"DNS", #"NT", #"ND", #"NH", nil];
static NSString *CellIdentifier = #"resultsDisplayCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
MarksFromMeets *athleteMarks = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString* date = [ITrackHelperMethods dateToAbbreviatedString:athleteMarks.meetDate];
NSMutableString *title = [NSMutableString stringWithFormat:#"%#", athleteMarks.markInEvent];
NSMutableString *subTitle = [NSMutableString stringWithFormat:#"%# - %#",date, athleteMarks.meetName];
[title replaceOccurrencesOfString:#"(null)"
withString:#""
options:0
range:NSMakeRange(0, [title length])];
// cell.imageView.image = athleteMarks.photoThumbNail; // Don't like image in front of record.
[cell.textLabel setFont:[UIFont
fontWithName:#"Helvetica Neue" size:18]];
[cell.detailTextLabel setFont:[UIFont fontWithName:#"Helvetica Neue" size:16]];
[cell.detailTextLabel setTextColor:[UIColor grayColor]];
// make selected items orange
if ([athleteMarks.eventPR integerValue] != 0
&& (![exceptions containsObject:athleteMarks.markInEvent])) {
title = [NSMutableString stringWithFormat:#"%# (PR)",title];
[cell.textLabel setTextColor:[UIColor redColor]];
}
else if ([athleteMarks.eventSB integerValue] != 0
&& (![exceptions containsObject:athleteMarks.markInEvent])) {
title = [NSMutableString stringWithFormat:#"%# (SB)",title];
[cell.textLabel setTextColor:[UIColor orangeColor]];
} else {
[cell.textLabel setTextColor:[UIColor grayColor]];
}
cell.textLabel.text = title;
cell.detailTextLabel.text = subTitle;
cell.indentationLevel = indentationLevelOne;
cell.indentationWidth = indentationForCell;
return cell;
}
If you want, I can send you an example of a Category for an Entity's NSManagedObject Sub-Class. This Category loads the photo and the thumbnail into CoreData Entity. The first time will be slow. However, after that the user should be able to scroll through TableView smoothly and then all the updated results will load automatically. Let me know.
One nice thing is that CoreData handles all the memory management.
Good luck!
I don't have enough rep to comment, So here's an answer which helped my tableview scrolling performance:
Make the tableview height larger than the viewable window. Cells will load "off screen" and helps improve scroll smoothness.
Do your image processing in the following method:
-(void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
Those two tricks got my table flowing really nice. I'm getting my image data from an API service and AFNETWORKING has an awesome image loader, but not necessary for you since images are in the bundle.
Maybe you could try SDWebImage instead. It is also a xamarin component
which fashions an asynchronous image downloader and asynchronous memory and disk image caching with automatic cache expiration handling. Using it would probably mean throwing away a lot of hard written code, but it might be worth it -plus your code will become a lot simpler. In iOS you can also setup a SDWebImageManager inside the viewDidLoad of a controller:
- (void)viewDidLoad{
...
SDWebImageManager *manager = [SDWebImageManager sharedManager];
manager.delegate = self;
...
}
and set the view controller as the delegate. Then, when the following delegate method is called:
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL
you could scale your images to thumbs of the appropriate size before caching them.
Hope that helps.
Weel I had a similar problem, my scroll was not smooth. I am inserting in the table a variable UIImageView with inside labelViews.
What I did was to change the method HeightforRowAtIndexPath for estimatedHeightforRowAtIndexPath and now scroll is smooth.

tableView cell image which is asynchronous loaded flickers as you scroll fast [duplicate]

This question already has answers here:
Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling
(13 answers)
Closed 9 years ago.
Im using a asynchronous block (Grand central dispatch) to load my cell images. However if you scroll fast they still appear but very fast until it has loaded the correct one. Im sure this is a common problem but I can not seem to find a away around it.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Load the image with an GCD block executed in another thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]];
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *offersImage = [UIImage imageWithData:data];
cell.imageView.image = offersImage;
});
});
cell.textLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] title];
cell.detailTextLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] subtitle];
return cell;
}
At the very least, you probably want to remove the image from the cell (in case it is a re-used cell) before your dispatch_async:
cell.imageView.image = [UIImage imageNamed:#"placeholder.png"];
Or
cell.imageView.image = nil;
You also want to make sure that the cell in question is still on screen before updating (by using the UITableView method, cellForRowAtIndexPath: which returns nil if the cell for that row is no longer visible, not to be confused with the UITableViewDataDelegate method tableView:cellForRowAtIndexPath:), e.g.:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.imageView.image = [UIImage imageNamed:#"placeholder.png"];
// Load the image with an GCD block executed in another thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]];
if (data) {
UIImage *offersImage = [UIImage imageWithData:data];
if (offersImage) {
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (updateCell) {
updateCell.imageView.image = offersImage;
}
});
}
}
});
cell.textLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] title];
cell.detailTextLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] subtitle];
return cell;
Frankly, you should also be using a cache to avoid re-retrieving images unnecessarily (e.g. you scroll down a bit and scroll back up, you don't want to issue network requests for those prior cells' images). Even better, you should use one of the UIImageView categories out there (such as the one included in SDWebImage or AFNetworking). That achieves the asynchronous image loading, but also gracefully handles cacheing (don't reretrieve an image you just retrieved a few seconds ago), cancelation of images that haven't happened yet (e.g. if user quickly scrolls to row 100, you probably don't want to wait for the first 99 to retrieve before showing the user the image for the 100th row).
This is a problem with async image loading...
Let's say you have 5 visible rows at any given time.
If you are scrolling fast, and you scroll down for instance 10 rows, the tableView:cellForRowAtIndexPath will be called 10 times. The thing is that these calls are faster than the images are returned, and you have the 10 pending images from different URL-s.
When the images finally come back from the server, they will be returned on those cells that you put in the async loader. Since you are reusing only 5 cells, some of these images will be displayed twice on each cell, as they are downloaded from the server, and that is why you see flickering. Also, remember to call
cell.imageView.image = nil
before calling the async downloading method, as the previous image from the reused cell will remain and also cause a flickering effect when the new image is assigned.
The way around this is to store the latest URL of the image you have to display on each cell, and then when the image comes back from the server check that URL with the one you have in your request. If it is not the same, cache that image for later.
For caching requests, check out NSURLRequest and NSURLConnection classes.
I strongly suggest that you use AFNetworking for any server communication though.
Good luck!
The reason of your flicker is that your start the download for several images during the scrolling, every time you a cell is displayed on screen a new request is performed and it's possible that the old requests are not completed, every tile a request completes the image is set on the cell, so it's if you scroll fast you use let's say a cell 3 times = 3 requests that will be fired = 3 images will be set on that cell = flicker.
I had the same issue and here is my approach:
Create a custom cell with all the required views. Each cells has it's own download operation. In the cell's -prepareForReuse method. I would make the image nil and cancel the request.
In this way for each cell I have only one request operation = one image = no flicker.
Even using AFNetworking you can have the same issue if you won't cancel the image download.
The issue is that you download the image on the main thread. Dispatch a background queue for that:
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(myQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]];
//UI updates should remain on the main thread
UIImage *offersImage = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *offersImage = [UIImage imageWithData:data];
cell.imageView.image = offersImage;
});
});

How do I keep data in a UICollectionView when the cells go outside the screen?

I have a CollectionView which displays some info about users. One of these items is a user avatar (100x100 png), which is loaded from our webserver. The problems is that it seems to me the view reloads the cells when they have gone outside the screen region. Every time I scroll the page it lags for a bit when the cells that were entirely outside the screen region enter the screen. It's not a really big problem on wifi, but on 3g this is really slow, plus it consumes bandwidth. Overall, it's just bad to handle data like this for my app. I want to load the data once and display it, and not have hundreds of webrequests when the user scrolls the page. Is there any way to load the cells once, and leave them like that until I update them manually? I don't do any updating yet, this is my current code:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return numContacts;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
UILabel *contactName = (UILabel *)[cell viewWithTag:1];
char *text = contactArray[indexPath.row].name;
contactName.text = [NSString stringWithCString:text encoding:NSASCIIStringEncoding];
NSData * presence = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"offline" ofType:#"png"]];
UIImageView *presenceView = (UIImageView *)[cell viewWithTag:2];
presenceView.image = [UIImage imageWithData: presence];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"myWebServer",text]];
NSData * avatar = [[NSData alloc] initWithContentsOfURL:url];
UIImageView *avatarView = (UIImageView *)[cell viewWithTag:3];
avatarView.image = [UIImage imageWithData: avatar];
NSData *placeholder = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"avatar_unknown" ofType:#"png"]];
[avatarView setImageWithURLRequest:[NSURLRequest requestWithURL:url] placeholderImage:[UIImage imageWithData:placeholder] success:nil failure:nil];
return cell;
}
You are loading the image data in the main queue, so you are blocking it, and thus the interface freezes.
Take a look at the answer to this question: how to send Asynchronous URL Request?
I think instead you should attempt to make one asynchronous service call when your View Controller is loaded to retrieve the data and store it (ex. in an NSMutableArray), because obviously making a service call everytime a cell is loaded is a poor solution. You could even just adjust your Contact custom object (I'm assuming you've created one to store relevant contact info) to include an image property to store the avatar Image. You can't 'load all the cells' because they're reloaded as they appear on the screen.
I rewrote part of the code so I can retrieve all the images at the start, and just display them when my view loads. This solved both problems.

UICollection View shows different results on Device than on Simulator

I'm trying to figure out what might be happening in the following code segment. I have a collection view and I'm showing saved images as thumbnails. I'm also inserting a stock "New Image" icon at the end so the user can add an additional image.
On the iPad simulator it appears fine but when I run on the device I do not see the "New Image" icon. I can touch it to open up the camera view controller for capture/selection but the image does not display. Any ideas? Here's the code...
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *)[[self sectionImagesCollectionView] dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
if ([indexPath row] < [sectionImages count]){
// Have an image so assign the photograph
RFHSectionImages *sectionImage = [sectionImages objectAtIndex:[indexPath row]];
UIImage *image = [UIImage imageWithData:[sectionImage photograph]];
[[cell imageView] setImage:image];
} else {
// Use the standard image
UIImage *newPhoto = [UIImage imageNamed:#"New Image.png"];
[[cell imageView] setImage:newPhoto];
}
return cell;
}
Here's a small detail that many developers have wasted hours upon:
Case Sensitivity
Really, it all boils down to:
iPhone Simulator: Case-Insensitive
iOS device: Case-Sensitive
So, you might have 'New Image.png' in your code, but the actual image name is 'new image.png'. It might even be something like 'nEw iMaGe.png'.
This will work fine on the simulator, but not on the device - the device simply won't find the image.
This has happened to me for everything from images to plists.
Check your Cases!
I had a similar problem, but the culprit was network latency — had to manually call self.collectionView.reloadData() to get things showing up. Locally, the data apparently loaded quickly enough that this wasn’t an issue.

Resources