I have added this UICollectionViewDelegate with my implementation:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
SAPCollectionViewCell *collectionViewCell = (SAPCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"SAPCollectionViewCell" forIndexPath:indexPath];
NSInteger index = indexPath.item + indexPath.section * 2;
NSString *imagePath = [_images objectAtIndex:index];
collectionViewCell.index = index;
[collectionViewCell setImageWithPath:imagePath];
collectionViewCell.delegateCell = self;
return collectionViewCell;
}
In my custom cell I have this method:
- (void)setImageWithPath:(NSString *)imagePath
{
if (!self.imageView)
{
CGRect rect = self.contentView.frame;
self.imageView = [[UIImageView alloc] initWithFrame:rect];
[self.contentView addSubview:self.imageView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageFromPath:imagePath];
UIImage *resizedImage = [UIImage imageWithImage:image scaledToWidth:rect.size.width];
dispatch_async(dispatch_get_main_queue(), ^{
[self.imageView setImage:resizedImage];
});
});
}
}
So as you can see if the cell does not have UIImageView then I start to create it and in background I get image from local path and resize it to cell content view width, then in main queue I set image to UIImageView.
This part work good but when I try to scroll my UICollectionView e.g. with 10 images I noticed that for example if first 2 top images were disappeared when I scroll down and then when I scroll back to the top, the images are changed placed.
This is state of images before scrolling down:
And this state after I scroll UIColectionView back to the top:
So as you can see first to item changed theirs location. As I have set if (!self.imageView) in my code above it should means that the image never will be created twice or more times.
I opened debugger and was checking this method:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
So the images paths returned one by one as it worked when UICollectionView just created cells at first time.
At first time first UICollectionViewCell has 0x8fbc1d0
and second has 0x8d0a4c0 address
But when I scroll down and then scroll up the debugger shown me second address at first 0x8d0a4c0 and then shown me 0x8fbc1d0
But I can't understand why items change theirs order. Or maybe here is another issue?
Also I don't have any method in the code that make for example for me reorder for cell. just few UICollection view methods delegate that configure count of cells and create them.
Also if I remove if (!self.imageView) seems everything works good, but then my code every time invoke dispatch that's no good for me, because when I resize image to the cell size I don't need do it twice.
When you scroll the two items off screen, your CollectionView puts the associated cells back onto the 'queue'. When you scroll back up again, the dequeueResuableCellwithReuseIdentifier in cellForItemAtIndexPath retrieves them from that queue, but (as you've seen) not necessarily in the same order. Thus the cell which was item 0 has been used for item 1 and vice versa. As you've seen, the if (!self.imageView) actually stops the correct image from being loaded. If you want to avoid retrieving and resizing them each time, then I would store the (resized) imageViews in an array.
- (void)setImageWithPath:(NSString *)imagePath
{
if (!self.imageView)
{
CGRect rect = self.contentView.frame;
self.imageView = [[UIImageView alloc] initWithFrame:rect];
[self.contentView addSubview:self.imageView];
}
if (![self.imagePath isEqualsToString:imagePath] && self.imageView.image == nil)
{
self.imagePath = imagePath;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageFromPath:imagePath];
UIImage *resizedImage = [UIImage imageWithImage:image scaledToWidth:rect.size.width];
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.imagePath isEqualsToString:imagePath])
{
[self.imageView setImage:resizedImage];
}
});
});
}
}
Related
I have created collection view programmatically where i have created UIImageview programmatically within collectionViewCell and images are displayed in imageview are downloaded from server asynchronously.
Code below is written in cellForItemAtIndexPath: method -
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSURL *imageURL = [NSURL URLWithString:[arrmImgPaths objectAtIndex:indexPath.row]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
// [imgvMainView removeFromSuperview];
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell addSubview:imgvMainView];
[imgvMainView setImage:image];
imgvMainView.contentMode = UIViewContentModeScaleAspectFit;
});
});
Problem is, when i scroll collection view some images are overlapped on one another. Please tell me solution to avoid it.
Thanks in advance.
Please use below line I hope this would work what I am assuming problem of duplicate cell.
UIImageView* imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell.contentView addSubview:imgvMainView];
This is most likely happening because you are reusing cells (correctly) and that the previous use of the cell is still calling the previous image. You must cancel the async task when the cell goes out of view. By the way, it will make you life much easier to use AFNetworking+UIImageView to do this.
One other option is to check that there is no UIimageView in your cell before you create one.
dispatch_sync(dispatch_get_main_queue(), ^{
UIView *viewToRemove;
for (UIView *view in cell.subViews) {
if (view isKingOfClass:UIImageView){
viewToRemove = view;
}
}
[viewToRemove removeFromSuperView];
imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell addSubview:imgvMainView];
[imgvMainView setImage:image];
imgvMainView.contentMode = UIViewContentModeScaleAspectFit;
});
I have a question about table view cells.
In cell I have an UIImageView. This view loads from web service with getter:
- (UIImageView *)imageView
{
if (!_ imageView)
{
_imageView = [[TurnipImageView alloc] init];
_imageView.backgroundColor = [UIColor clearColor];
}
[_imageView loadImageViewFromWebService];
return _imageView;
}
When I add [_imageView loadImageViewFromWebService]; call inside of if (!_ imageView) statement, image view is not loaded correctly.
When I scroll table view, cells are reloading as well as image views and causing lags in scrolling.
Maybe anyone knows how to optimise this process?
You should try lazy loading of the table view :
here is the sample : https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html
also have a look at this :
Lazy loading UITableView with multiple images in each cell
Try this
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
//perform download on background thread
dispatch_async(dispatch_get_main_queue(), ^{
//assign image to imageview on main thread, UI operations should be carried on main thread
});
});
- (UIImageView *)imageView
{
if (!_ imageView)
{
_imageView = [[TurnipImageView alloc] init];
_imageView.backgroundColor = [UIColor clearColor];
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
//perform download on background thread
[_imageView loadImageViewFromWebService];
});
return _imageView;
}
Hope this helps
I am working with UICollectionView where I am displaying all the images from photo library of the phone.
When I click on any of the image, the image get flipped and and some information regarding the image is displayed.
When the user again clicks on the same image the image flips again and the original image is shown.
The problem is that whenever I scroll down through the UICollectionView the last selected image flips automatically and the information about the image gets displayed.
How to stop this problem.
Here is some code:
- (void) collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell1 = [collectionView cellForItemAtIndexPath:indexPath];
if(old_path!=NULL){
UICollectionViewCell *cell2 = [collectionView cellForItemAtIndexPath:old_path];
[UIView transitionFromView:cell2.selectedBackgroundView toView:cell2.contentView duration:0.5 options:UIViewAnimationOptionCurveLinear |UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
}
if(old_path==indexPath&&flag)
{
[cell1 setSelected:NO];
[UIView transitionFromView:cell1.selectedBackgroundView toView:cell1.contentView duration:0.5 options:UIViewAnimationOptionCurveLinear |UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
flag=FALSE;
}
else{
[UIView transitionFromView:cell1.contentView toView:cell1.selectedBackgroundView duration:0.5 options:UIViewAnimationOptionCurveLinear |UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
flag=TRUE;
}
old_path=indexPath;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
ALAsset *asset = assets[indexPath.row];
NSLog(#"Description : %#",[asset description]);
UIImage *img=[self imageWithImage:[UIImage imageWithCGImage:[asset thumbnail]] convertToSize:CGSizeMake(150, 150)];
UIView *contents = [[UIView alloc]initWithFrame:cell.bounds];
contents.backgroundColor = [UIColor colorWithPatternImage:img];
[cell.contentView addSubview:contents];
UIView *backgroundView = [[UIView alloc]initWithFrame:cell.bounds];
backgroundView.backgroundColor = [UIColor yellowColor];
UIButton *del=[UIButton buttonWithType:UIButtonTypeRoundedRect];
del.frame= CGRectMake(backgroundView.frame.origin.x+20, backgroundView.frame.origin.y+20, 100, 40);
[del setTitle:#"Delete" forState:UIControlStateNormal];
[del addTarget:self action:#selector(delete) forControlEvents:UIControlEventTouchUpInside];
[backgroundView addSubview:del];
UIButton *cancel=[UIButton buttonWithType:UIButtonTypeRoundedRect];
cancel.frame= CGRectMake(backgroundView.frame.origin.x+20, backgroundView.frame.origin.y+80, 100, 45);
[cancel setTitle:#"Cancel" forState:UIControlStateNormal];
[cancel addTarget:self action:#selector(cancel) forControlEvents:UIControlEventTouchUpInside];
[backgroundView addSubview:cancel];
cell.selectedBackgroundView = backgroundView;
[cell bringSubviewToFront:cell.selectedBackgroundView];
return cell;
}
Here, old_path contains the index of the last selected image.
The main problem is probably with the UIView transition methods:
[UIView transitionFromView:cell1.selectedBackgroundView toView:cell1.contentView duration:0.5 options:UIViewAnimationOptionCurveLinear |UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
UICollectionViewCell's contentView and selectedBackgroundView shouldn't be messed with like this, because the cell manages their layout. This code will remove the content view entirely and replace it with the background view, which is expected to be behind the content view when selected, and removed from the view hierarchy when not selected.
The proper way to accomplish what you're doing (showing / hiding the image in response to a tap) would be perform the transition between the image view itself and another subview on the content view.
There could also be something in your resizing code that's flipping the image, but it's hard to say without the code for imageWithImage:convertToSize:. It would probably be more efficient to get rid of that method, and do something like this:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:cell.bounds];
imageView.contentsMode = UIViewContentModeScaleAspectFill;
imageView.clipsToBounds = YES;
imageView.image = [UIImage imageWithCGImage:[asset thumbnail]];
[cell.contentsView addSubview:imageView];
A couple of other observations:
Collection view cells are reused, which means that your implementation collectionView:cellForItemAtIndexPath: could end up adding a whole pile of image views to a cell that has been dequeued several times. A better solution would be to subclass UICollectionViewCell and add the custom views in its init method.
The code old_path==indexPath does not actually test for equality between the two index paths, just if the two variables have the same address in memory. Use [oldPath isEqual:indexPath] instead.
You cannot do custom animations on background views without subclassing and handling explicitly. Because collection view automatically brings the selectedBackgroundView to top if selectedBackgroundView is not same as backgroundView. Adding apple documentation for reference.
// The background view is a subview behind all other views.
// If selectedBackgroundView is different than backgroundView, it will be placed above the background view and animated in on selection.
https://developer.apple.com/library/iOs/documentation/UIKit/Reference/UICollectionViewCell_class/Reference/Reference.html
In my ipad app, i has 200 images and i add these images into an array.
Then add this array into image view by looping.
Then, i add this image view as sub view of scroll view.
When i open app, my app is crash.
I try with reducing image size. But, it didn't work.
One of my friend told firstly i should add only image1 and image2.
When user scroll image1, then it show image2.
After that image1 is remove from image view.
And add image3 to image view.
He told it can maintain memory usage.
But, i don't know how can i do that? :D
Please, give me some example.
Thanks in advance.
My code is here,
- (void)viewDidLoad
{
[super loadView];
self.view.backgroundColor = [UIColor grayColor];
UIScrollView *ScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 44, self.view.frame.size.width, self.view.frame.size.height)];
ScrollView.pagingEnabled = YES;
// Create a UIImage to hold Info.png
UIImage *image1 = [UIImage imageNamed:#"Image-001.jpg"];
UIImage *image2 = [UIImage imageNamed:#"Image-002.jpg"];
UIImage *image200 = [UIImage imageNamed:#"Image-200.jpg"];
NSArray *images = [[NSArray alloc] initWithObjects:image1,image2,...,image200,nil];
NSInteger numberOfViews = 200;
for (int i = 0; i < numberOfViews; i++)
{
CGFloat xOrigin = i * self.view.frame.size.width;
UIImageView *ImageView = [[UIImageView alloc] initWithFrame:CGRectMake(xOrigin, 0, self.view.frame.size.width, self.view.frame.size.height-44)];
[ImageView setImage:[images objectAtIndex:i]];
[ScrollView addSubview:ImageView];
}
ScrollView.contentSize = CGSizeMake(self.view.frame.size.width * numberOfViews, self.view.frame.size.height);
[self.view addSubview:ScrollView];
It looks like you're accessing the images array at indexes 0..200, but it contains only three elements. Either set numberOfViews = 3;, or index into the array like this:
ImageView.image = images[i%3];
The modulo index will cause you to add each of the three images in a repeating sequence and still let you test with a larger number of scrollView subviews. I doubt that your crash is about memory with only 200 images, unless they are super-huge ones.
You should use a table view to display your images. You don't want to create an array of all those images, because that puts all those images into memory -- not a good thing. You don't even need to create an array to hold the names, since they have names you can easily construct from an integer (which you can get from indexPath.row in the tableView:cellForRowAtIndexPath: method). Something like this:
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 200;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *imageName = [NSString stringWithFormat:#"Image-%.3d.jpg",indexPath.row +1];
UIImage *image = [UIImage imageNamed:imageName];
cell.imageView.image = image;
return cell;
}
This example just uses the cell's default imageView to show the images, which is pretty small. You would probably want to create a custom cell with a larger image view in it (or a couple side by side if you want more than one image per row).
I am trying to asynchronously load an image in a UITableVIew.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = nil;
NSString *cellIndetify = [NSString stringWithFormat:#"cell%d",tableView.tag -TABLEVIEWTAG];
cell = [tableView dequeueReusableCellWithIdentifier:cellIndetify];
IndexPath *_indextPath = [IndexPath initWithRow:indexPath.row withColumn:tableView.tag - TABLEVIEWTAG];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndetify];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.contentView.backgroundColor = [UIColor blackColor];
// here I init cell image view
UIView *cellSub = [self.waterFlowViewDatasource waterFlowView:self cellForRowAtIndexPath:_indextPath];
cellSub.backgroundColor = [UIColor blackColor];
[cell.contentView addSubview:cellSub];
cellSub.tag = CELLSUBVIEWTAG;
}
// fill image info
[self.waterFlowViewDatasource waterFlowView:self fillDataIntoCellSubview:[cell viewWithTag:CELLSUBVIEWTAG] withIndexPath:_indextPath];
CGFloat cellHeight = [self.waterFlowViewDelegate waterFlowView:self heightForRowAtIndexPath:_indextPath];
CGRect cellRect = CGRectMake(0, 0, _cellWidth, cellHeight);
[[cell viewWithTag:CELLSUBVIEWTAG] setFrame:cellRect];
[self.waterFlowViewDatasource waterFlowView:self relayoutCellSubview:[cell viewWithTag:CELLSUBVIEWTAG] withIndexPath:_indextPath];
return cell;
}
in that ImageView class I init:
(id)initWithIdentifier:(NSString *)indentifier
{
if(self = [super initWithIdentifier:indentifier])
{
self.backgroundColor = [UIColor blackColor];
if (!self.imageView) {
_imageView = [[UIImageView alloc] init];
self.imageView.backgroundColor = IMAGEVIEWBG;
[self addSubview:self.imageView];
self.imageView.layer.borderWidth = 1;
self.imageView.layer.borderColor = [[UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.0] CGColor];
}
......some others controllers
}
-(void)setImageWithURL:(NSURL *)imageUrl{
UIImage *cachedImage = [[SDImageCache sharedImageCache] imageFromKey:[imageUrl absoluteString]];
if (cachedImage) {
self.imageView.image = cachedImage;
}else{
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:imageUrl delegate:self options:SDWebImageCacheMemoryOnly userInfo:[[NSDictionary alloc] initWithObjectsAndKeys:[imageUrl absoluteString], #"image_url", [NSValue valueWithBytes:&imageSize objCType:#encode(CGSize)], #"image_size", [NSNumber numberWithInt:arrIndex], #"imageview_index", nil]];
_imageView.image = [UIImage imageNamed:#"album_detail_placeholder.png"];
}
.....
.....
- (void)imageDecoder:(SDWebImageDecoder *)decoder didFinishDecodingImage:(UIImage *)image userInfo:(NSDictionary *)userInfo {
imageView.image = image
}
As you see here I used SDWebImageManager, but that doesn't seem to be the problem.
When pull up to next page, before the next page's images have loaded, if I scroll back to previous page's loaded image, the image will be covered by another image (which is should display in the newest page, not current page...) (for example, in the first page, I have a image and there is cat in this image, and then I scroll down till pull up to request next page, and after get all next page image's url, start asynchronous thread download images, before the newest images downloaded, scroll back to top, maybe first page. After awhile, some images downloaded, they should display in there cell, but now the images will cover the current page images, maybe the cat image now have a dog image on it) what shall I do?
When a row is scrolled off-screen, the table view reuses that row's cell for another row that has scrolled on-screen.
The problem is that you are using the ImageView as the delegate of the background loader, and the ImageView is associated with a cell, not a row. By the time the background loader finishes loading the image, the cell may have been reused for another row.
You need to make the table view's data source be the delegate for the background image loader. It should put the index path of the row into the userInfo dictionary when it calls downloadWithURL:delegate:options:userInfo:. When the downloader sends imageDecoder:didFinishDecodingImage:userInfo: to the data source, the data source should get the index path out of the userInfo and use it to ask the table view for the cell currently displaying that row (by sending cellForRowAtIndexPath: to the table view).