Reputation: 780
In my app the user can take a photo of their pet, or whatever, and that will show in the collection view as a circular picture. Using cellForItemAtIndexPath to check for the existence of a user generated photo (which is stored in the Documents directory as a png file), if not display a default photo.
Issues:
- When Scrolling the UICollectionView the image load halts the
smoothness of the scroll UI as the picture is going to appear on
screen.
- If you scroll the picture off the screen many times the
app crashes due to memory pressure.
QUESTION: Is there an efficient way of loading and keeping the images in memory without having to keep loading the image after the first time it is displayed?
I thought rasterizing the images would help, but it seems the UI scrolling and memory issues persist.
Here is the code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifierPortrait = @"allPetCellsPortrait";
AllPetsCell *cell = (AllPetsCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifierPortrait forIndexPath:indexPath];
CGRect rect = CGRectMake(cell.petView.center.x, cell.petView.center.y, cell.petView.frame.size.width, cell.petView.frame.size.height);
UIImageView *iv = [[UIImageView alloc] initWithFrame:rect];
//set real image here
NSString *petNameImage = [NSString stringWithFormat:@"%@.png", self.petNames[indexPath.section][indexPath.item]];
NSString *filePath = [_ad documentsPathForFileName:petNameImage];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
iv.image = nil;
if (fileExists){
[_allPetsCollectionView performBatchUpdates:^{
if (iv && fileExists) {
iv.image = [UIImage imageWithContentsOfFile:filePath];
}
} completion:^(BOOL finished) {
//after
}];
//loadedImage = YES;
[collectionView layoutIfNeeded];
}//file exists
else{
iv.image = [UIImage imageNamed:[NSString stringWithFormat:@"%@", self.contents[indexPath.section][indexPath.item]]];
[cell.petNameLabel setTextColor:[UIColor darkGrayColor]];//be aware of color over pictures
shadow.shadowColor = [UIColor whiteColor];
}
cell.petView.clipsToBounds = YES;
UIColor *color = MediumPastelGreenColor;
if ([[NSString stringWithFormat:@"%@", self.contents[indexPath.section][indexPath.item]] isEqualToString:@"addpet.png"]) {
color = MediumLightSkyBlueColor;
}
[cell.petView setBounds:rect forBorderColor:color];
[cell.petView addSubview:iv];
if (![[NSString stringWithFormat:@"%@", self.contents[indexPath.section][indexPath.item]] isEqualToString:@"addpet.png"])
[iv addSubview:cell.petNameLabel];
//TODO: optimize the cell by rasterizing it?
if ([_contents count] > 4) {
cell.layer.shouldRasterize = YES;
}
return cell;
}
Here is a screen shot:
Thanks!
Upvotes: 2
Views: 1252
Reputation: 1226
Assigning an image to the collection view cell's imageView doesn't cost you any performance issues at all..
There are 2 problems here.
You are fetching the contents (datasource for the collection view) from within the collection view's delegate method. This is causing the delay.
You are adding the imageView holding the image again and again. Thus, each time the cell is displayed, a new imageView holding a newly fetched data is being added as the subView to the reused cell (As the new imageView covers the old one as both have the same frame, you wouldn't notice. And at the sametime this eats up memory). This is causing the memory pressure crash.
SOLUTION
FOR IMPROVING PERFORMANCE
Fetch the image contents from your model class(if you are using one), or from you viewController's viewDidLoad() (You should never fetch datasource from within the collection view's delegate method). Perform the fetching process in the background queue as it wouldn't affect your UI in any manner.
Store the contents onto an array and fetch the contents only from the array to populate your collection view cells.
FOR AVOIDING MEMORY CRASH
As long as you are not using a custom collection view cell, its better to remove all the subviews from the collectionViewCell's content view each time the cell is dequeued.
I hope this solves your problem :)
Upvotes: 1
Reputation: 780
I recently discovered NSCache which is a dictionary type object that I can store my *already loaded image*s into and check to see if they are loaded with the -cellForItemAtIndexPath method.
I changed the above code to accommodate for this also by using a custom dispatch queue that would be sent to the main queue. This still causes a slight pause bump in the scrolling when the initial image is loaded but after that it is read from the image cache. Not totally optimal yet but I think I am headed in the right direction.
here is the code:
if (fileExists){
//DONE: implement NSCACHE and dispatch queue
const char *petImageQ = "pet image";
dispatch_queue_t petImageQueue = dispatch_queue_create( petImageQ, NULL);
//check to see if image exsists in cache - if not add it.
if ([_petImagesCache objectForKey:petNameImage] == nil) {
dispatch_async(petImageQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView performBatchUpdates:^{
iv.image = [UIImage imageWithContentsOfFile:filePath];
} completion:^(BOOL finished) {
//add to cache
[_petImagesCache setObject:iv.image forKey:petNameImage];
}];
});
});
}
else{
//load from cache
iv.image = [_petImagesCache objectForKey:petNameImage];
}
I hope this helps. Please add more to this for improving it!
Upvotes: 0