Lag in UICollectionView on Scroll - ios

I'm using UICollectionView for showing some images stored in NSMutableArray
Here is my code for cellForItemAtIndexPath
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = (CollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"CollectionViewCell" forIndexPath:indexPath];
if (indexPath.row<self.bookMarksArray.count) {
cell.bookmarkImage.image = [self.bookMarksimages objectAtIndex:indexPath.row];
cell.bookmarkTitle.text =[self.bookMarkNames objectAtIndex:indexPath.row];
}else
{
UIImage *bookMarkImage=[UIImage imageNamed:#"emptyCell.png"];
cell.bookmarkImage.image = bookMarkImage;
cell.bookmarkTitle.text =#"";
}
cell.removeBtn.enabled = NO;
cell.delegate = self;
return cell;
}
I have maximum 32 items in array self.bookMarksImages,When i'm scrolling my collection view, i can notice that there is a lag on scroll (for only first time).
But when i replaced line cell.bookmarkImage.image = [self.bookMarksimages objectAtIndex:indexPath.row];
with a static image cell.bookmarkImage.image = [UIImage imageNamed:#"emptyCell.png"];
i can see there is no lag on scroll and my collection view is scrolling smoothly .
here is my code for bookmarkImage initialisation.
-(void)getAllBookmarkImages
{
self.bookMarksimages = [[NSMutableArray alloc] init];
self.bookMarkNames = [[NSMutableArray alloc] init];
NSArray *bookMarksArray = [BookMarks getBookMarks];
for (int i=0; i<self.bookMarksArray.count; i++) {
NSDictionary *bookmarkInfo = [bookMarksArray objectAtIndex:i];
UIImage *bookMarkImage=[UIImage imageWithContentsOfFile:[BookMarks documentsPathForFileName:[bookmarkInfo objectForKey:#"IMAGE"]]];
[self.bookMarksimages addObject:bookMarkImage];
[self.bookMarkNames addObject:[bookmarkInfo objectForKey:#"TITLE"]];
}
}
Why array self.bookMarksimages is causing lag?

There are many factors which is causing this issue
1.Image size
2.Image Location
A Workaround is load the image in async thread with help of image path.make a array of image path,instead image
- (void) loadImageFromPath:(NSString*)path{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
NSData *data= [NSData dataWithContentsOfFile:path];
UIImage *theImage=[UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
cell.bookmarkImage.imagee=theImage;
});
});
}
Usage
[cell loadImageFromFile:[self.bookMarksimages objectAtIndex:index]];

Related

Failed to load image in a UICollectionView

I need to show flickr public images in a UICollectionView. I have done all the things, but images don't load in the UICollectionView. I am new to objective-c. Please help.
authorIds = [[NSMutableArray alloc] init];
dateTaken = [[NSMutableArray alloc] init];
smallImageUrlArray = [[NSMutableArray alloc] init];
largeImageUrlArray = [[NSMutableArray alloc] init];
smallImageArray = [[NSMutableArray alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumLineSpacing = 10;
layout.minimumInteritemSpacing = 10;
layout.sectionInset = UIEdgeInsetsMake(110, 20, 20, 20);
_collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
[_collectionView setDataSource:self];
[_collectionView setDelegate:self];
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
[_collectionView setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:_collectionView];
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return totalImageNo;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
UIImageView *cellImageView = (UIImageView *)[cell viewWithTag:100];
cellImageView.image = [UIImage imageNamed:[smallImageUrlArray objectAtIndex:indexPath.row]];
//[self.view addSubview:cellImageView];
[cell.backgroundView addSubview:cellImageView];
// NSLog(#"%#",[smallImageUrlArray objectAtIndex:indexPath.row]); //Here can show Img's values correctly
cell.backgroundColor=[UIColor darkGrayColor];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(160, 160);
}
In below I am fetching Flickr images and storing in array. then want to load the smallImageArray in the UICollection view. but nothing shows up.
-(void)fetchFlickrPublicImages
{
NSString *urlString = [NSString stringWithFormat:#"http://api.flickr.com/services/feeds/photos_public.gne?format=json&tags=data"];
NSURL *url = [NSURL URLWithString:urlString];
NSData *badJSON = [NSData dataWithContentsOfURL:url];
NSString *dataAsString = [NSString stringWithUTF8String:[badJSON bytes]];
NSString *correctedJSONString = [NSString stringWithString:[dataAsString substringWithRange:NSMakeRange (15, dataAsString.length-15-1)]];
correctedJSONString = [correctedJSONString stringByReplacingOccurrencesOfString:#"\\'" withString:#"'"];
NSData *correctedData = [correctedJSONString dataUsingEncoding:NSUTF8StringEncoding];
NSError* error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:correctedData options:kNilOptions error:&error];
NSArray *images = [json objectForKey:#"items"];
for (NSDictionary *image in images)
{
NSString *authorId = [image objectForKey:#"author_id"];
[authorIds addObject:(authorId.length > 0 ? authorId: #"Untitled")];
NSString *largeImageUrl = [NSString stringWithFormat:#"%#",[[image objectForKey:#"media"] objectForKey:#"m"]];
NSString *smallImageUrl = [largeImageUrl stringByReplacingOccurrencesOfString:#"_m" withString:#"_s"];
[smallImageUrlArray addObject:smallImageUrl];
[dateTaken addObject:[NSString stringWithFormat:#"%#",[image objectForKey:#"date_taken"]]];
[largeImageUrlArray addObject:[NSString stringWithFormat:#"%#",[[image objectForKey:#"media"] objectForKey:#"m"]]];
totalImageNo = authorIds.count;
}
for (int i=0; i< smallImageUrlArray.count; i++) {
UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfURL:
[NSURL URLWithString:[smallImageUrlArray objectAtIndex:i]]]];
[smallImageArray addObject:image];
}
NSLog(#"%#", smallImageArray);
NSLog(#"%ld", (long)totalImageNo);
// NSLog(#"authorIds: %#\n\n", authorIds);
// NSLog(#"photoURLsLareImage: %#\n\n", smallImageUrlArray);
// NSLog(#"photoURLsLareImage: %#\n\n", largeImageUrlArray);
}
You should add your UIImageView to the collection view cell's contentView, NOT backgroundView.
In your code ,
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UIImageView *cellImageView = (UIImageView *)[cell viewWithTag:100];
cellImageView.image = [UIImage imageNamed:[smallImageUrlArray objectAtIndex:indexPath.row]];
[cell.backgroundView addSubview:cellImageView];
}
you are assigning image as url . but in your method named fetchFlickrPublicImages where you are downloading images directly.
better you write code like
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UIImageView *cellImageView = (UIImageView *)[cell viewWithTag:100];
cellImageView.image =(UIImage*)[smallImageArray objectAtIndex:indexPath.row];
[cell.contentView addSubView cellImageView];
}

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];

Uilabel is not visible in collectionview

Im using the following code to load images from url into collection view
-
(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100];
UILabel *recipeLabel = (UILabel *)[cell viewWithTag:200];
if ([ImageArray count] >0){
for(int i = 0; i < [ImageArray count]; i++)
{
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;
});
});
}
}else{
UILabel *title = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, cell.bounds.size.width, 40)];
title.text = #"No image record found";
title.tag = 200;
[title setHidden:true];
[cell.contentView addSubview:title];
}
[spinnerShow stopAnimating];
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
return cell;
}
If the ImageArray has got some url, if loop will be executed. Else the else loop will be executed where it will display a label saying no image record found. But the uilabel is not getting visible
Why are you using for loop in cellForItemAtIndexPath? This method is called for every item in UICollectionView. In else condition, add log or breakpoint and check if it is going there or always going in if condition. Make sure if you need to check for array count or want to check if image exists on URL?

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.

UICollectionView doesn't display any image

I'm trying to display image from documents directory in UICollectionView, everything works fine, but no image appears
- (void)viewDidLoad
{
[super viewDidLoad];
// Initialize recipe image array
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
NSMutableArray *image = [NSMutableArray array];
for (NSString *tString in dirContents) {
if ([tString hasSuffix:#".png"]) {
[image addObject:tString];
listeImages = [NSArray arrayWithArray:image];
}
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return listeImages.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImageView *listeImageView = (UIImageView *)[cell viewWithTag:100];
UILabel *LabelView = (UILabel *)[cell viewWithTag:110];
LabelView.text = [listeImages objectAtIndex:indexPath.row];
listeImageView.image = [UIImage imageNamed:[listeImages objectAtIndex:indexPath.row]];
NSLog(#" List UIImageView listeImageView.image %#",listeImageView.image);
cell.backgroundColor = [UIColor redColor];
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"photo-frame.png"]];
NSLog(#" List NSArray *listeImages %#",listeImages);
cell.tag = indexPath.row;
return cell;
}
with this piece of code, displays the correct UICollectionView UILabel well but no image is displayed.
I use this line of code to store my image in listeImageView.image.
UIImageView *listeImageView = (UIImageView *)[cell viewWithTag:100];
listeImageView.image = [UIImage imageNamed:[listeImages objectAtIndex:indexPath.row]];
Here are the results of my NSLog:
2014-10-11 09:46:31.117 imgapp[2803:60b] List NSArray *listeImages; (
"image10102014233607.png",
"image10102014233616.png",
"image10102014233627.png"
)
2014-10-11 09:46:31.158 imgapp[2803:60b] List UIImageView listeImageView.image (null)
2014-10-11 09:46:31.171 imgapp[2803:60b] List UIImageView listeImageView.image (null)
2014-10-11 09:46:31.178 imgapp[2803:60b] List UIImageView listeImageView.image (null)
any help will be appreciated.
Thanks
You have below,
listeImageView.image = [UIImage imageNamed:[listeImages objectAtIndex:indexPath.row]];
But your doing,
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"photo-frame.png"]];
Why your not using your listeImageView as below:-
cell.backgroundView = listeImageView;
That should get you the image.
UPDATE :-
for (NSString *tString in dirContents) {
if ([tString hasSuffix:#".png"]) {
**[image addObject:[UIImage imageWithContentsOfFile:tString] ]; //Do this as in below line you were not create a image object which you are trying to get.**
//Also check your image array for object, that is it getting stored or not.
// [image addObject:tString];
}
}
listeImages = [NSArray arrayWithArray:image]; //Do this after you have added all objects in your image mutable array and not in for loop as this will initialise it many tiems depending on your loop run count.
You're not adding your listeImageView to anything.
Try it with
[cell addSubview:listeImageView];
or if you really want it to be the backgroundView
cell.backgroundView = listeImageView;
But it'd be much cleaner if you create a custom UICollectionViewCell-class where you can define what content this cell has.
Have u tried to give background color to cell instead of backgroundview.do that once if possible with same code of yours!
Or
You can try like :-UIImageView *listeImageView = (UIImageView *)[cell viewWithTag:100];
[cell bringviewtoFront:listeImageView ];
You'd be much better off subclassing UICollectionViewCell, and setting up the subviews in a storyboard. Your code could be much cleaner this way:
static NSString *kCustomCellIdentifier = #"customcell";
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MYAPPCustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCustomCellIdentifier forIndexPath:indexPath];
// Load custom image
NSString *imageName = [listeImages objectAtIndex:indexPath.row];
if (imageName)
{
UIImage *image = [UIImage imageNamed:imageName];
if (image)
{
cell.customImageView.image = image;
}
else
{
// Set an empty state here
cell.customImageView.image = [UIImage imageNamed:#"errorImage.png"];
}
}
return cell;
}

Resources