I am working in xcode 8.3. I have a CollectionView. I have downloaded images from web sevice and placed the images in each CollectionView cells. But when i scrolling the CollectionView, images in the each cells are changing. After a few minutes it shows the correct image. I have tried many solutions available in stackoverflow. But i didnt get a solution. Please help me.
Like the others are saying its most likely because you are dequeuing reusable cells (As you should be) and setting the cell.imageView.image property to your web image.
The issue here is that because iOS is saving on memory by "reusing" these cells, they are literally the same cells in memory. So as it scrolls off one edge of the screen and disappears. As the new cell scrolls on instead of creating a new seperate cell it simply uses the one that it already has that just left the screen. Meaning your old image is still the one displayed in the cell.
Standard practice is setting the content of the cell in the cellForRowAtIndexPath: method. But if you are setting it to an image that is fetched asynchronously its entirely possible(likely) for the cell to appear on the screen with the old image before the new one is fetched. Presumably once the images are downloaded its not so much of an issue anymore as they should return instantly from a cache.
The simple fix here would be to either nil out the image before setting it each time in the cell, or preferably use a placeholder image.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CELL_IDENTIFIER" forIndexPath:indexPath];
// This will clear any existing image from the cell about to be displayed it is an option
//[cell.imageView setImage:nil];
if (ImageExistsLocally)
{
cell.imageView.image = [UIImage imageWithContentsOfFile:#"SomeImagePath"];
}
else
{
[cell.cellImageView sd_setImageWithURL:yourRemoteImage.thumbnailUrl
placeholderImage:[UIImage imageNamed:PlaceHolderImageName]
completed:nil];
}
return cell;
}
Note that sd_setImageWithURL is from the SDWebImage library that I think someone else mentioned here. https://cocoapods.org/pods/SDWebImage
It is because of reusing cells. Try to reset the image in your cell class prepareForReuse method
-(void)prepareForReuse {
[super prepareForReuse];
self.imageView.image = nil;
}
The problem you are facing is due to the Reuse of UITableViewCell.
If you are downloading images from web service use AlamofireImage or SDWebImage. It will handle your problem.
UICollectionView reuses the same UICollectionViewCell to improve performance. Only the data inside the UICollectionViewCell is changed, so before using the UICollectionViewCell, the UICollectionViewCell has to be cleared of its previous data. Cocoa Framework provides a method that is present in UICollectionViewCell that triggers every time when the UICollectionViewCell is to be reused.
just override the function given below in the .m file of your custom UICollectionViewCell class file
-(void)prepareForReuse {
[super prepareForReuse];
// here we clear the previously set data
self.imageView.image = nil; // this will clear the previously set imageView is a property of UIImageView used to display a image data
}
You can use prepareForReuse() method to handle this issue.
override func prepareForReuse() {
super.prepareForReuse()
// still visible on screen (window's view hierarchy)
if self.window != nil { return }
imageView.image = nil
}
Notes: If you are using SDWebImage, you should add this line to cancel current cell image download.
imageView.sd_cancelCurrentAnimationImagesLoad()
Related
Hello colleagues Apple developers!
I am creating an iOS application for both iPhone & iPad. The performance of UITableView and UICollectionView is very important feature in my application.
After long time spent for optimising, human eye can't tell the difference while scroll happens. Although, profiler still finds some issues.
The first inefficient thing is dequeuing. I have a UITableViewCell object, which user interface is created by using .xib files and auto layout constraints. Although, the time profiler instrument complains about the performance while dequeuing this specific cell.
One more problem I can't understand is a NSString setting as UILabel's text. This setter method is executed in method - tableView:cellForRowAtIndexPath:.
One more problem, that might be related with previous one, is a UIImage setting as UIImageView's image. This method is also executed in method - tableView:cellForRowAtIndexPath: and doesn't trigger any download or etc.
UITableViewCell objects, described above, are default size & really simple. The content view of the cell contains only 2 subviews: UILabel and UIImageView.
Images are downloaded according to Apple's example.
// Asks the data source for a cell to insert in a particular location of the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeues & initializes category table view cell.
CategoryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CategoryTableViewCellIdentifier];
APICategory *category = [self.categories objectAtIndex:indexPath.row];
if (category.thumbnail)
{
[cell updateThumbnailWithCategory:category];
}
else
{
if (!tableView.dragging && !tableView.decelerating)
{
[category thumbnailWithSuccess:^(UIImage *thumbnail)
{
if ([tableView.visibleCells containsObject:cell])
{
[cell updateThumbnailWithCategory:category];
}
}
failure:^(NSError *error)
{
// Handle thumbnail receive failure here!
}];
}
[cell updateThumbnailWithPlaceholder];
}
cell.category = [self.categories objectAtIndex:indexPath.row];
return cell;
}
Specific images used in this case are .png images 300x300 resolution and 24 kb of size.
These are the problems related with the performance of UITableView and UICollectionView.
Can anyone explain me, what could be the reasons for any of those issues?
Also, is there a way to improve it?
I am using XCode 6.3.1 targeting iOS 7.
I am using AFNetworking's UIImageView category to download images with an unknown dimension to UITableViewCell's. Here is a sample image:
The issue I am having is that since the dimensions of the image is unknown, I just use a placeholder image. If the placeholder image has the exact same dimensions, then there is no issue. However, if the dimensions are different, there are issues with spacing in the cell.
If the image is smaller than there will be too much spacing. Here is an example:
I don't know how to refresh the cell after I finish downloading the image so that the spacing is per my Auto Layout Constraints.
If I scroll away from the cell and scroll back, the spacing is fine.
Here is some sample code for the downloading of the image
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Temp
static NSString *Cell = #"CustomListCell";
CustomListCell *cell = (CustomListCell *)[self.tableView dequeueReusableCellWithIdentifier:Cell];
CustomListRow *customListRow = self.customList.rows[indexPath.row];
// Reset the cell
cell.headerImageView.image = nil;
cell.titleLabel.text = #"";
// Download the image, placeholder image is necessary
NSString *topImageURL = #"sample_image";
__weak __typeof(cell)weakCell = cell;
if ([topImageURL isEqualToString:#""] || [topImageURL isEqualToString:#"false"])
{
// Do nothing
} else {
[cell.headerImageView setImageWithName:topImageURL afterManipulation:^UIImage *(UIImage *image) {
// Manipulation
UIImage *newImage = [UIImage expandImage:image toWidth:Constants.screenWidth - 16];
// CustomListCell *updateCell = (CustomListCell *)[tableView cellForRowAtIndexPath:indexPath];
// if (updateCell)
// updateCell.headerImageView.image = newImage;
return newImage;
} placeholderImage:[UIImage expandImage:Constants.placeholderImage toWidth:Constants.screenWidth - 26]];
}
return cell;
}
setImageWithName:afterManipulation:placeholderImage: is a method I made to wrap around AFNetworking's setImageWithURL.... It first checks of the image exists locally before checking two different URL's (absolute and base_url + relative) if the image exists there.
I put the manipulation block in there so that I could call a UIImage category method I created that will scale the image to fit the width of the UITableView (so the only dynamic part is the height).
Here is a list of things I have tried:
Reloading the particular cell
reloading the entire table
[self.tableView beginUpdates] + [self.tableView endUpdates];
Calling [cell setNeedsLayout], or [cell setNeedsDiplay];
setNeedsLayout and setNeedsDisplay didn't do anything once I finished loading the image (I placed it in the afterManipulation block which is called before the image is assigned, and I have also tried placing it after the image is assigned).
Reloading the cell, the table, or beginUpdates causes some really weird behavior to occur. The cells start getting mixed together and some cells have the same picture (which shouldn't happen). I'm not sure what is happening, but my guess is that reloading the cell causes image to download again (or pull it from the cache) which doesn't finish until after another cell is loaded.
Have you thought about playing with the contentMode property of the image view?
A flag used to determine how a view lays out its content when its bounds change.
An issue that I observed while looking at your implementation is that you might get wrong images in the cells. I see you take the cell as a reference when downloading the image. This is wrong, and here is why:
Table view cells get reused so when you scroll the cells that get off screen will be used again to display the information for other rows. By taking a reference to the cell and not the indexpath, if your download takes time, when the completion block is called, that cell may be displaying information for a different row and thus, the image you apply on it may not be the right one.
You should have a look at Apple's example of how to keep consistency while downloading images for every table view cell: https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009394-Intro-DontLinkElementID_2
I am using UICollectionView to display images.
Here is my code:
- (WaterFallCollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
WaterFallCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:collectionViewCellIdentifier forIndexPath:indexPath];
NSURL *itemTBURL = [NSURL URLWithString:self.items[indexPath.item][#"image"][#"thumbnailLink"]];
[cell setNeedsLayout];
cell.imageView.imageURL = itemTBURL;
return cell;
}
My problem is, for example, it has totally 10 images in collectionView, first I scroll down to last one, which the first image in collectionView will be invisible, when I scroll up to the first one, it has the refreshing effect and the program will jump into the method I showed. It looks like when the cell turns from invisible to visible state, iOS will force to call the method. I want to store the previously displayed cell, so when you scroll up, it won't have the refresh effect. What should I do? I know Twitter, Pinterest, Facebook they looks like wont' refresh the previous cells when you scroll up. I know it is feasible, just don't know how to do that. Thanks in advance.
What is "refresh effect"? What do you mean?
Everytime you scroll to new UICollectionViewCell, UICollectionView call cellForItemAtIndexPath: to get this cell.
Every cell is reusable. So, you can initialize cell for further reuse it in "- (void) prepareForReuse()" method of cell.
As far as I understand, every time you set imageView.imageURL - image start loading and after that, you show it, right? I think, you should cache this images after first loading.
In any case, there is no way to skip calling cellForItemAtIndexPath.
I'm using UICollectionView in one of my projects and when I try to call reloadData on the collection view, the cell order is always changed the same.
I create the cells something like that:
- (UICollectionViewCell *)collectionView:(UICollectionView *)aCollectionView
cellForItemAtIndexPath:(NSIndexPath *)aIndexPath {
PPhotoCVCell *cell =
[self._collectionView dequeueReusableCellWithReuseIdentifier:#"PPhotoCVCell"
forIndexPath:aIndexPath];
if (cell.photo == nil) {
PPhoto *photo = self._photos[aIndexPath.row];
cell.photo = photo;
}
cell.enableEditing = self._editing;
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
return cell;
}
The cell has two subviews of the class UIImageView, one for the image and another for an overlay if the cell is selected by the user.
When I'm in editing mode, the user can select cells. When some cells are selected and the user quits the editing mode, I set the alpha value of the overlay image view with an animation to 0.0 in the overwritten setSelected: method.
The problem is when I call the reloadData method on the collection view, the overlay image views in the selected cells hide without animation, but in other cells overlay image views appear without animation and disappear with the correct animation.
I detected that the image view for the photo is also changing when I call reloadData.
Is there any solution for this problem?
I ran into this same issue where each time I performed reloadData, the collection view would reverse it's order. Since I didn't see an answer here after 5 years, I'm posting my solution in case anyone else runs into this.
You actually have 2 options:
If you only need to reload a single cell, use the following instead of reloadData:
NSArray *thisItem = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:thisRow inSection:0]];
[self.collectionView reloadItemsAtIndexPaths:thisItem];
If you need to reload all data, use the following:
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
There's probably a reasonable explanation for this behavior somewhere, but I can't find one.
It sounds like your cells are being recycled. In your cell's prepareForReuse, make sure you're removing the overlay. Then add the overlay as appropriate in collectionView: cellForItemAtIndexPath:.
I have an UICollectionViewController and my custom cells, and in my cellForRowAtIndexPath: method I set the cells based on indexPath.row.
But I am getting wrong results, this cell appears even after first position, and if you scroll back and forth, it pops up in random places. How do i fix that?
Here is the code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DVGCollectionViewCell *cell;
cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
if (indexPath.row == 0)
{
cell.imageView.image = [UIImage imageNamed:#"something1.png"];
cell.buyLabel.text = #"170";
cell.textLabel.text = #"11.2011";
}
return cell;
}
Cell in both UITableView and UICollectionView are recycled, that means that when one goes off screen it is put in an NSSet until you need it again. When it's need it's removed from the set ad added again at UICollectionView views hierarchy. If you do not clean the value inside the cell or set them again, the cell will show the same data when it was created.
This is made for performance reason creating cell takes more time instead of value them again.
If your problem is in layout check the layout flow object, which size did you set?
I have found the problem, once the cell contents was set it was never cleaned. So I added cleaning every cell properties as additional clause and it works fine.
You can perform any clean up necessary to prepare the view for use again if you override prepareForReuse in your custom cell implementation.
One of the answers in this SO post helped: override prepareForReuse and reset the cell to its default state. Don't forget to call super.prepareForReuse.