UICollectionView App crashes showing large number of images on iPhone - ios

I need to display around 300 images on UICollectionView. I have added custom UICollectionViewCell and subview UIImageView, now when i try to display images on UICollectionView from web server it display around 150 images and then app crashes showing low level memory. Below is my code, please let me know what i am doing wrong.
-(void)updateView {
for (int i = 0; i < m_nPhotoCount; i ++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *imageURL = [NSURL URLWithString:[aryList objectAtIndex:i]];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
if(image == nil)
{
NSLog(#"kashif");
}
else {
[self.imageArray addObject:image];
}
dispatch_sync(dispatch_get_main_queue(), ^{
[collectionView reloadData];
});
});
}
}
#pragma mark - UICollectionView Datasource
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [self.imageArray count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"GalleryCell";
GalleryCell *cell = (GalleryCell*)[cv dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.imageView.image = [self.imageArray objectAtIndex:indexPath.row];
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
return [[UICollectionReusableView alloc] init];
}

You are taking up the wrong approach. What you should do is, You should download only those number of images (equivalent to number of visible cells of CollectionView) at once.
When collection view is scrolled than download more images and purge or store previous images in the cache.
You can make use of this wonderful Image Downloading and caching library : SDWebImage

There are special libraries available for gallery. You should try library rather handling or creating collection view with large size.

Related

UITableViewCell Resizing is not smooth

I am trying to change UICollectionViewCell size depending on a slider's value. Right now, I am managing this by calling reloadData on my UICollectionView each time my slider has its value changed. The problem is that with big data sources, the refresh is not smooth and sometimes free the application for a little time. Is there any way to enhance this ? I specify that I have images in my cells. Here is the code I wrote :
- (IBAction)didChangeCellSize:(UISlider *)sender
{
[self.collectionView reloadData];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
float size = 120.0 * (self.cellSizeSlider.value + 1);
return CGSizeMake(size, size);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ProductCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"productCollectionViewCell" forIndexPath:indexPath];
if ((self.filteredProducts == nil && self.products.count > 0 && indexPath.row < self.products.count) || (self.filteredProducts && self.filteredProducts.count > 0 && indexPath.row < self.filteredProducts.count))
{
NSDictionary *product;
NSData *imageData;
if (self.filteredProducts)
{
product = [self.filteredProducts objectAtIndex:indexPath.row];
}
else
{
product = [self.products objectAtIndex:indexPath.row];
}
imageData = product[ParseDataManagerItemImageData];
if (imageData)
{
UIImage *image = [UIImage imageWithData:imageData];
if (image)
{
cell.productImageView.image = image;
}
else
{
cell.productImageView.image = [UIImage imageNamed:#"DefaultCartItem"];
}
}
else
{
cell.productImageView.image = [UIImage imageNamed:#"DefaultCartItem"];
}
if (self.editMode)
{
cell.deleteButton.hidden = NO;
}
else
{
cell.deleteButton.hidden = YES;
}
cell.productNameLabel.text = [product[DataManagerItemTitle] isKindOfClass:[NSString class]] ? product[DataManagerItemTitle] : #"";
cell.indexPath = indexPath;
cell.productsVC = self;
}
return cell;
}
As confirmed in the question comment, loading image is the cause of the problem.
Allocating an image array first, can solve the problem if products.count is relative small. Or you will need some kind of 'smarter' cache service running in the background to serve the correct images needed on screen, because I saw you're also using a filter.
Loading the images in a separate thread should help in both cases, so that you can determine whether to load extra files from data or from existing cached versions, maybe an array or other kind, and throw away unused images if your cells can be modified later.
I think you don't need to reload all the cells. Only reload the cells which are visible on screen or currently active. Try this if its work.
NSArray *visibleCellIndexPaths = [collectionView indexPathsForVisibleItems];
[collectionView reloadItemsAtIndexPaths:visibleCellIndexPaths]

UICollectionCellView - Bringing repeated Images

I am downloading some images from parse to a UICollectionView. The problem is that the cells are being reused before the images are downloaded. How can I cancel the request and avoid repeating the same images on different cells?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
WallImage *wallImageFromUser = [DataStore instance].wallImagesFromUser[indexPath.row];
static NSString *identifier = #"Cell";
UserPhotoCollectionViewCell *cell = (UserPhotoCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
if ([[DataStore instance].wallImagesFromUser count] > 0) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void){
UIImage *postedImage = [UIImage imageWithData:[wallImageFromUser.imageFile getData]];
dispatch_async(dispatch_get_main_queue(), ^(void){
[cell.userPhotoButton setImage:postedImage forState:UIControlStateNormal];
});
});
}
return cell;
}
I have tried to use [wallImagesFromUser.imageFile cancel] but with no success.
Any suggestions?
According to me you should create your cell identifier name as your customcell name. it is better approach to avoid repeating of cell in your app. and try to this
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"UserPhotoCollectionViewCell";
UserPhotoCollectionViewCell *cell = (UserPhotoCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void){
WallImage *wallImageFromUser = [DataStore instance].wallImagesFromUser[indexPath.row];
UIImage *postedImage = [UIImage imageWithData:[wallImageFromUser.imageFile getData]];
dispatch_async(dispatch_get_main_queue(), ^(void){
[cell.userPhotoButton setImage:postedImage forState:UIControlStateNormal];
});
});
return cell;
}
but i prefer SDWebImage to download image in cell of collectionview/tableview
you can use SDWebImage third party to download image in your collectionview cell's image. it download image once and set image to that particular cell. it reduce the problem of repeating image in collectionview cell. it improve your app performance also. you can download here
https://github.com/rs/SDWebImage

UICollectionView cells being skipped

I'm trying to create CollectionView similar to 500px app.
App logic: After app is launched, CollectionView loads empty cells(around 100), then when user scrolls down, cells are filled with image. At the end of view, new batch of empty cells are created(Load More). Images are pulled from Instagram, 20 per call.
Problem: When new images are pulled(async), cell rendering continues and usually it manages to create 3-5 empty cells before next batch of images are available.
Question: Is there any solutions for this problem? Maybe there's a better way to achieve this?
Code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ALPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"photo" forIndexPath:indexPath];
cell.backgroundColor = [UIColor grayColor];
if (indexPath.row < [self.photos count]) {
cell.photo = self.photos[indexPath.row];
}
if (indexPath.row == [self.photos count]-6) {
[ALLoadMore loadMore:self.nextPage input:self.photos completionHandler:^(NSArray *newLoad, NSString *next) {
self.photos = newLoad;
self.nextPage = next;
NSLog(#"%#", self.nextPage);
dispatch_async(dispatch_get_main_queue(), ^{
//[self.collectionView reloadData];
if ([self.photos count] > indexPath.row) {
cell.photo = self.photos[indexPath.row];
NSLog(#"call");
}
NSLog(#"%lu", (unsigned long)[self.photos count]);
});
}];
}
return cell;}
For now cell count is a static number:120

Collection view working on both iphone but half showing in ipad

In my application,I am using Collection view as i'd take in Xib.now there is image of array come from server side. And they are correctly seen in both iphone 3.5 and 4 inch screen.But when i select device as ipad it seem like this:
There are three rows appear and i don't know how to solve this problem.I want only one row which will be scrollable.
Here is my code:
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [imagArr count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
QBAssetsCollectionViewCell *cell = (QBAssetsCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"QBAssetsCollectionViewCell" forIndexPath:indexPath];
NSDictionary *imageName = [[imagArr objectAtIndex:indexPath.row] objectForKey:#"Content"];
NSString *sr = [imageName objectForKey:#"text"];
cell.Img.image = [self decodeBase64ToImage:sr];
//[cell updateCell];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%ld",(long)indexPath.row);
NSDictionary *imageName = [[imagArr objectAtIndex:indexPath.row] objectForKey:#"Content"];
NSString *sr = [imageName objectForKey:#"text"];
cloud.image= [self decodeBase64ToImage:sr];
[self.collectionView setHidden:YES];
}

UICollectionViewCell subclass containing UIImageView outlet not displaying image

I have a UICollectionViewCell subclass called AlbumCVC that contains a single IBOutlet --- a UIImageView called cellView. I'm setting the value of cellView for each cell inside the following method:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"albumPhotoCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor blueColor];
if ([cell isKindOfClass:[AlbumCVC class]]){
AlbumCVC *albumCVC = (AlbumCVC *)cell;
ALAsset *thisImage = [self.albumPhotos objectAtIndex:indexPath.item];
albumCVC.imageView.frame = albumCVC.contentView.frame;
albumCVC.contentView.contentMode = UIViewContentModeScaleAspectFit;
albumCVC.imageView.image = [UIImage imageWithCGImage:[thisImage aspectRatioThumbnail]];
}
}
return cell;
}
where albumPhotos is an NSMutableArray of ALAssets. I'm sure that the property is getting set correctly because I get sensible results when I log the albumCVC.cellImage.image.bounds.size. Cells are also sized properly as the frames are visible when I set the background color. But for some reason, cellImage won't display. Is there another method call I need to make inside collectionView:cellForItemAtIndexPath: in order to get the image to show up?
Update: On the advice of a very smart friend, I tried moving the UIImageView out of the cell, putting it elsewhere in the main view, and everything worked lovely. The problem appears to have something to do with the frame / bounds of the UIImageView. I think there's a method call I need to make so that the cell's subview expands to fit the newly-resized cell following the call to collectionView:layout:sizeForItemAtIndexPath:. The problem now is that UIImageView.image.size is a read-only property, so I can't resize it directly.
Update 2: On another piece of advice I looked at the frame and bounds of the cell's contentView and cellImage and found that they weren't matching up. Added another method call to make them equal, and even changed contentMode to UIViewContentModeScaleAspectFit in order to try and get the cell to render the thumbnail properly. Unfortunately, I'm still getting tiny thumbnails inside huge cells. Any idea why? Updated code above and below.
For the sake of completeness, here's the entire class implementation:
#import "AlbumViewController.h"
#import "AlbumCVC.h"
#import <AssetsLibrary/AssetsLibrary.h>
#interface AlbumViewController ()
#end
#implementation AlbumViewController
#pragma constants
const int IPHONE_WIDTH = 320;
#pragma delegate methods
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section{
// Get the count of photos in album at index albumIndex in the PhotoHandler
NSInteger numCells = [self.group numberOfAssets];
return numCells;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"albumPhotoCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor blueColor];
if ([cell isKindOfClass:[AlbumCVC class]]){
AlbumCVC *albumCVC = (AlbumCVC *)cell;
ALAsset *thisImage = [self.albumPhotos objectAtIndex:indexPath.item];
}
albumCVC.imageView.frame = albumCVC.contentView.frame;
albumCVC.contentView.contentMode = UIViewContentModeScaleAspectFit;
albumCVC.imageView.image = [UIImage imageWithCGImage:[thisImage aspectRatioThumbnail]];
}
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
//Copy a pointer to an asset from the album
ALAsset *thisImage = [self.albumPhotos objectAtIndex:indexPath.item]; //Zen - are you sure thisImage represents a valid image?
//Copy that asset's size and create a new size struct
CGSize thisSize = thisImage.defaultRepresentation.dimensions;
CGSize returnSize;
// force all previews to be full width
returnSize.width = IPHONE_WIDTH;
returnSize.height = IPHONE_WIDTH * thisSize.height / thisSize.width;
return returnSize;
}
#pragma lifecycle methods
- (void)viewWillAppear:(BOOL)animated{
[self.albumPhotos removeAllObjects];
//"handler" is a class that manages calls to the ALAssetLibrary. self.albumIndex is an integer that gets set on segue. As far as I can tell, everything in the below method is working fine --- cells are sized properly.
self.group = self.albumDelegate.handler.groups[self.albumIndex];
[self.group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
NSLog(#"Just added an object to albumPhotos.");
[self.albumPhotos addObject:result];
NSLog(#"The item in albumPhotos is class %#", [self.albumPhotos[0] class]);
}
}];
}
#pragma instantiation
- (ALAssetsGroup *)group{
if (!_group) {
_group = [[ALAssetsGroup alloc]init];
}
return _group;
}
- (NSMutableArray *)albumPhotos{
if (!_albumPhotos) {
_albumPhotos = [[NSMutableArray alloc]init];
}
return _albumPhotos;
}
#end
Update 3: I can't be certain what the problem was initially, but I know that it now works with the following cellForItem implementation:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"albumPhotoCell" forIndexPath:indexPath];
if ([cell isKindOfClass:[AlbumCVC class]]) {
AlbumCVC *albumCVC = (AlbumCVC *)cell;
albumCVC.albumImageView.image = [[UIImage alloc] initWithCGImage:[self.albumAssets[[self reverseAlbumIndexForIndex:indexPath.item]] thumbnail]];
}
cell.alpha = [self alphaForSelected:self.selectedItems[#(indexPath.item)]];
return cell;
}
There's no screwing around with frames or bounds anywhere, everything just works. Maybe it's the difference between [[UIImage alloc]initWithCGImage] and [UIImage imageWithCGImage]?
I've had a similar issue and resolved it by setting the UICollectionViewCell frame property to be the same as the UIImageView's frame. I'm not 100% sure that this is your issue, I was building the collection purely in code (no Storyboard)

Resources