This question is for my understanding as my code is working fine.
I have looked inside SDWebImage, but it's fairly large and I can't pinpoint how the mechanism I'm questioning works.
OK, let's say I have a tableview full of UIImageViews (one inside each cell), and I call the SDWebImage Category/Extension on each of them to go and lazy load an image from the web.
What mechanism is employed to update the cell as it's on screen with the newly downloaded image, without reloading the tableview?
I ask this as I was surprised to see that when using SDWebImage Extension each of my cells' imageViews image popped into existence as soon as it's corresponding image had downloaded.
I was under the impression that I'd have to reload the tableView, but instead each cells imageView 'automagically' updated when the image was available!
How does this work? Does SDWebImage keep a reference to each cell/imageView it's working with?
SDWebImage inserts the loaded image into the UIImageView instance on which the load has been queried.
With UITableViewCell you have to be a bit tricky to avoid non-relevant images in your cells, here is why:
Imagine you requested the image for URL of the first item (firstURL) on the topmost visible cell.
You scroll down your table, and following happens:
the former topmost cell gets reused and appears on the bottom of the table.
the image is queried for URL of the last cell (lastURL).
firstURL loading completed, and corresponding image is inserted into the image view of the last cell, because it was the image view for which firstURL loading has been queried.
lastURL loading completed, and corresponding image is inserted into the image view of the last cell.
Steps 3 and 4 might look like fast blink in the image view.
To avoid that, you need to address the cancellation of the previous download in prepareForReuse method implementation of the UITableViewCell subclass.
e.g.
- (void)prepareForReuse {
[super prepareForReuse];
[self.imageView sd_cancelCurrentImageLoad];
self.imageView.image = <placeholder image>;
}
If you are referring UIImageview in UITableviewCell then you can check that SDWebImage is one kind of UIImageView class, so no need to describe which image view refers for downloaded image, as its self identify
Let me show you once..
as we request for image inside cell like this.
[cell.imgBrand sd_setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
}];
When first line executed, it will call method inside UIImageView+WebCache.h class.
as you can see that class itself UIImageView
#implementation UIImageView (WebCache)
Class For SDWebCache UIImageView
Related
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()
I used SDWEBImage library to show images in scrollview under the tableview.
My problem is, images load completely but when I scroll tableview flashy images from previous cells or duplicate images show in Imageview. I programmatically created an imageview like below:
My code is below:
for (NSString *stringurl in [[[Arr objectAtIndex:indexPath.row]valueForKey:#"sports"]valueForKey:#"image"]) {
UIImageView *yourImageView =[[UIImageView alloc] initWithFrame:CGRectMake(x,10,40,40)];
NSURL *urlimg=[NSURL URLWithString:stringurl];
[yourImageView sd_setImageWithURL:urlimg
placeholderImage:[UIImage imageNamed:#"placeholder"] options:indexPath.row ? SDWebImageRefreshCached : 0];
[cell.arenaimgscroll addSubview:yourImageView];
x = x + 50;
if (stringurl==NULL) {
cell.arenaimgscroll.hidden=YES;
}
else{
cell.arenaimgscroll.hidden=NO;
}
}
Just a short overview, So you get your answer
UITableView 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 UITableView, 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 UITableView, UITableView datasource method gets called again for every indexPath, thus dequeueReusableCellWithIdentifier gives you cached cell, which already have UIImageView added, but
you add UIImageView again to it(SOURCE OF ERROR)
Better solution will be
to add imageView in interface builder
or
you can check, if imageView already added and then skip the step of adding it again
UPDATE:
NOTE : This solution is not optimized as you are already not reusing image elements, but will resolve your problem.
Add this script above your for loop, this is for clearing older images(that got reused) from your scrollview(arenaimgscroll) :
[cell.arenaimgscroll.subviews makeObjectsPerformSelector: #selector(removeFromSuperview)];
for (NSString *stringurl in [[[Arr objectAtIndex:indexPath.row]valueForKey:#"sports"]valueForKey:#"image"]) {
//YOUR CODE
}
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 want to load an image onto a table view cell, i.e., a custom cell with an image view. And, I use SDWebImage. I am loading an image onto the cell without using setImageWithURL. This is the code inside cellForRowAtIndexPath.
[_imgManager downloadWithURL:urlArray[indexPath.row]
completed:^(UIImage *image, NSError *error//yada, yada) {
if(image)
{
NSLog(#"Image received");
//cell.pictureView.image = image; // doesn't work, so I did
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *tCell = [self.tableName cellForRowAtIndexPath:indexPath];
if(tCell)
tCell.imageView.image = image;
}); // doesn't work either
}
}];
So as I have mentioned in the comments, it doesn't work. What am I doing wrong? Or maybe my conception of this is wrong? The images load only after I scroll (that activates cellForRowAtIndexPath for other cells). And they keep refreshing on each appearance. It doesn't work exactly as expected.
I am loading in a list of facebook users using webcache and it works fantastically. Until you select one of the cells then it seems to either change the content mode, or more likely it changes the size of the uiimageview frame, but based on the actual size of the picture. for clarity here are some screens
here it is loaded
http://img.photobucket.com/albums/v246/homojedi/Screenshot20120727114807.png
and on selection of some of the images as you can see they seem to jump to their original aspect.
http://img.photobucket.com/albums/v246/homojedi/Screenshot20120727114827.png
as expected if i scroll them off screen and back to them they restore to what they were at the start.
It's baffling. The only thing i have not attempted is subclassing the uitableView and setting its layout subview there. short of that is there anything else i can do?
EDIT: code requested
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// typically you need know which item the user has selected.
// this method allows you to keep track of the selection
_indexPath = indexPath;
[_indexPath retain];
}
I've face exactly the same issue as you,and I fix
the issue is cause by you named your imageView "imageView"
i don't know why
if you change your imageView name like "m_imageView"
the issue fixed
It's all about naming!
I have come across this problem but I have no imageView property inside my custom UITableViewCell.
In fact I did make a stupid mistake by connecting an extra IBOutlet from my imageView object to the default imageView property of UITableViewCell. It is because I initially named my image view imageView, but then I changed it and forget about the outlet.
After removing the outlet, everything works fine.
Just in case someone else, like me, come upon this issue where non of the above worked.
I too had named my UIImageView: imageView. But, for some reason, even after renaming it, the problem persisted.
I tried everything I could think of, Reset Simulator, Clean Project, adding removing constrains....
As it turns out, simply deleting the UIImageView from the Storyboard and dragging a new one fixed it.
Couldn't have been a precompiled issue because I cleaned the project. Go figure.
I was having this issue and I solved it!!!
Check to make sure you synthesized the imageView property in your custom TableViewCell.
I forgot to synthesize a property (resulting in no getter/setter for the IBOutlet).
This topic is old but hope that helps someone else!
// reason for this problem is UITableView cell's imageview initially has a default size later on it resize it self on select so following cab be a solution for this: (Using AFNetworking to set image/ You can adjust accordingly)
__weak UItableViewCell *cellTemp = cell;
[cell.imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:strURL]]
placeholderImage:[UIImage imageNamed:#"placeholder"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
cellTemp.imageView.image = image;
CGRect frameRect = cellTemp.imageView.frame;
frameRect.origin.y = (cellTemp.frame.size.height - image.size.height)/2;
frameRect.size.width = image.size.width;
frameRect.size.height = image.size.height;
cellTemp.imageView.frame = frameRect;
[cellTemp layoutSubviews];
// this will fit your cell's label
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
}];
I believe your problem is due to the UIImageView. You should be setting the image once - cell.imageView.image = myImage. Do not set a highlightedImage. Set the contentMode to maintain the aspect ratio of your image: cell.imageView.contentMode = UIViewContentModeScaleAspectFit;, and verify that the contentStretch rect is 0,0,1,1 (the default).
It is possible the tableView is changing the contentMode of the UIImageView or its frame when it gets selected (for who knows why reasons). I would add NSLogs to both willSelectRowAtIndexPath and didSelectRowAtIndexPath, showing the same information: the imageView frame, the contentMode value (as an integer), etc. Somehow one or more of these values is changing on selection.
Adding to the answers regarding the naming issue:
As it has been said, the problem with the image view persists even after renaming it. But it is not required to delete the UIImageView from your xib or storyboard. You can simply ctrl-click the image view and remove the connection to property imageView that still exists.
The reason the link to imageView still exists seems to be that the imageView property still remains available after you delete it in your code. It's still there because it is inherited from UITableViewCell.
One last hint that sounds stupid but took me a few minutes to realize: Obviously all code using property name imageView will still work but will result in strange behavior! So double check if there's no code left using the old property name.
I had the same issue, and even after trying all the previous answers none of them worked out. Later i found out that the UITableViewController had multiple unused Outlets, so deleting the unused Outlets fixed the problem.
As requested by sebastian-Luczak I did come up a solution for resizing, but now there is an image quality inconsistency, it gets higher resolution only when you touch it, strange but not as bad or noticable, anyway here is the code.
N.B i Am using SDWebImage to load the pictures asynchronously
[cell.imageView setImageWithURL:[NSURL URLWithString:path] placeholderImage:[UIImage imageNamed:#"Icon.png"]success:^(UIImage *image)
{
cell.imageView.image = [image resizeImage:image newSize:CGSizeMake(38, 38)];
} failure:^(NSError *error)
{
}];
// If you subclass UITableViewCell you can get rid of this problem
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.bounds = CGRectMake(10,5,65,65);
self.imageView.frame = CGRectMake(10,5,65,65);
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
}