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.
Related
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
I have a TableView with custom cells, and for some reason there will be a Table View Cell added that doesn't calculate its height (like these two examples):
The Cell finally calculates it when I scroll down past the blank cell and then back up to where it was before, and I can see the Images "flashing" and placing themselves in the correct spot in the Table View. So I assume it has something to do with the image's in my Table View not loading immediately or something?
Seems to happen in my testing mostly when there are two Table View Cells next to each other in the Table View that have Images in them.
I've tried a bunch of different things, have you ever heard of this? Any idea how to fix? Thanks!
I'm using AutoLayout and Storyboard.
UPDATE
Per Request, adding cellForRowAtIndexPath code:
__weak TableViewCellTwo *wcell = api2Cell;
[wcell.imageViewPic
sd_setImageWithURL:[NSURL URLWithString:imageURL]
placeholderImage:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
// Put Log
}
];
UPDATE
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return tableView.rowHeight; // return the default height
}
I have custom UITableViewCell which made from xib file .
And I have UIImageView inside that cell which I set the fixed width and height and 40x40 and set to hidden
And then in cellForRowAtIndexPath datasource delegate. I check if my datasource for that cell contain a URL to image . If it has ,I set the ImageView to appear .And use SDWebImage to download the image and adjust the size for imageView.frame in the completion block when download complete
Actually, at first appearance it is working fine .I got the ImageView that is the same size and the downloaded image. But if I scroll down and then up to the upper cell again. It just re-appear to 40x40 size gain. Can anyone help ?
This is the code just in case if you are curious. I guess nothing wrong with the code the problem is how I set the UIImageView in .xib file but I don't know how to solve it.
// -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
if([item postImageURLString]){
void (^completionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType) = ^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
CGRect imageFrame = CGRectMake(cell.frame.size.width/3,cell.frame.size.height -5 -image.size.height, image.size.width, image.size.height);
[cell.postImageView setFrame:imageFrame];
};
[cell.postImageView setHidden:NO];
[cell.postImageView setImageWithURL:URL completed:completionBlock ];
}
I suspect it has something to do with the tableview reusing cells that have the 40x40 image view. It looks like SDWebImage will not run your completion block if the image is already cached, so if the tableview redraws the cell and your image is already cached, it never executes the resizing code. You need to add code to your cellForRowAtIndexPath method that checks to see if the image is already cached, and if it is then resize the image view.
I get images asynchronously with parse, and i insert them into an array with some other stuff.
I return this array to fill a tableView, and the rows has an UIImageView, but some images that i get asynchronously doest get loaded when i fill the table with the array in cellForRowAtIndexPath, so when i first look the tableView, i get the rows with an empty image in the cell, till i move the scroll in the tableView so cellForRowAtIndexPath is called again and those images i was set in the array are already loaded.
I get the images also if i do a IBAction with [self.tableView reloadData]; i get the images.
The problem is that i return the cell inmediatelly and the image is not loaded.
I'm using a custom TableViewCell.
I found something about "AsyncImageView" to load the image, but i dont know how.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Get the game for the row
GameModel *game = [self.currentMatches objectAtIndex:indexPath.row]; //HERE i load the images
MatchCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"GameCell" forIndexPath:indexPath];
cell.userImage.image = game.avatarp1; //I set the image to the cell but isnt loaded yet
//If i do this WORKS but i get 2 times the same image (on the game and now) (its a function that i created, that seems is calling tableView or so when assigns the image to the cell, i dont understand why it works
[ParseModel getAvatarWithUser:game.p1 completionBlock:^(UIImage *avatar) {
cell.userImage.image = avatar;
}];
What if you set to nil the image when you get the dequeueReusableCell, and then let the block set the image?
Just change this:
cell.userImage.image = game.avatarp1;
For this:
cell.userImage.image = nil;
And then let this code works (I have understood by your answer that this works well)
[ParseModel getAvatarWithUser:game.p1 completionBlock:^(UIImage *avatar) {
cell.userImage.image = avatar;
}];
Hope it helps!
You can use "SDWebImage" as an alternative to "AsyncImageView" that you mentioned in your question. It is very easy to implement. I put here an example of my own code, where I download an image from a URL in a cell in the same way you are trying. (Change my "view" for your cell.userImage)
This method puts a temp image (placeholder and when the image is fully downloaded, it automaticlly changes (without any reload or scroll).
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
if (view == nil)
{
view = [[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 238.0f, 289.0f)] autorelease];
//here is where you put a placeholder and download in the background the image with an URL. Items is an array of URLs
[(UIImageView *)view setImageWithURL:[items objectAtIndex:index]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
view.contentMode = UIViewContentModeScaleAspectFit;
}
return view;
}
When you have added SDWebImage to your project, dont forget to add into your .m: #import < SDWebImage/UIImageView+WebCache.h >
Here is a video of the problem: http://youtu.be/Jid0PO2HgcU
I have a problem when trying to set the image for the [cell imageView] in a UITableView from the asset library.
The image is loaded but it doesn't appear in the cell until I touch (select) that cell. Here is my code that sets the imageView of the cell:
//Start loading the image using the image path
NSString *path = [occasion imagePath];
if (path != nil && [path hasPrefix:#"ass"])
{
NSLog(#"photo loaded from assets library");
//testing ALAssestLibrary
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
ALAssetsLibraryAssetForURLResultBlock resultsBlock = ^(ALAsset *asset)
{
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
[[cell imageView] setImage:[image imageByScalingAndCroppingForSize:CGSizeMake(36.0, 42.0)]];
[image release];
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error){
NSLog(#"FAILED! due to error in domain %# with error code %d", error.domain, error.code);
// This sample will abort since a shipping product MUST do something besides logging a
// message. A real app needs to inform the user appropriately.
abort();
};
//Convert path to an NSUrl
NSURL *url = [NSURL URLWithString:path];
// Get the asset for the asset URL to create a screen image.
[assetsLibrary assetForURL:url resultBlock:resultsBlock failureBlock:failureBlock];
// Release the assets library now that we are done with it.
[assetsLibrary release];
}
The above code is part of the:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
Any ideas?
I think in this case you're using a iOS defined UITableViewCell (and not a custom one), that is one of those cells that you get from "initWithStyle:reuseIdentifier:". Now these cells are internally built in such way that if you don't assign some content immediately the corresponding subview will not be added in the cell's contentView hierarchy. This kind of behavior is typical of complex custom views, where the final cell layout can be determined only when all its subviews have their content fully specified. (E.g. you have two labels, let's say label A and label B, label A is multirow and you want to add label-B immediately below label-A, then you can place label-B correctly only once you have the label-A content and you calculate its height.). So I suppose the built-in iOS table cells are done in the same way and the subviews hierarchy is built at the "layoutSubviews" stage.
In your case you create the cell but defer the time the image is provided. But at this point the layoutSubviews has been called yet and without any image the corresponding imageView is not even allocated and added in the contentView hierarchy!
So, according to me, the solution for you is just to assign immediately a "placeholder" for your asset (the placeholder can be a transparent image of course) which will guarantee you of the internal UIImageView creation. Be careful with the image size, it should be close to the expected image size, for the same reason explained above. As soon as your finish block is called the image view should be already there and the image will appear.
The reason why when you tap on the cell the image appears, is due to the fact that this operation calls the layoutSubviews again. In this case the whole code is probably re-executed and as you already called "setImage:" before then the internal "image" property is set and the contentView hierarchy will be rebuilt with the new image.
Another possible solution to your problem is of course to use a "custom" UITableViewCell (e.g. loaded from a Nib). In such case all your subviews will be loaded and you will simply access to the UIImageView using his tag (read apple docs to know more about this useful technique to create table view cells from a Nib file).
you need to layout cell after image set
just like
[cell setNeedsLayout];
I scratched my head for so long and finally figured it out.
My mistake was that I was setting image in cell.imageView when I should be setting my actual outlet cell.eventImageView. It was messing with the generic imageview provided in UITableViewCell. Hope it helps somebody.
This is my way how i solved these problem, i know is not the best but that work :)
UIImage *thumbnailImage = ...
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imageView setImage:thumbnailImage];
UITableViewCellSelectionStyle selectionStyle = cell.selectionStyle;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell setSelected:YES];
[cell setSelected:NO];
cell.selectionStyle = selectionStyle;
});
In my case I was setting up a prototype cell through a Storyboard, but was accidentally using the dequeueCellWithIdentifier:forIndexPath method instead of dequeueCellWithIdentifier:.
The first method is only to be used when appropriating cells to a tableview through the programmatic UITableView registerClass:forReuseIdentifier: or registerNib:forReuseIdentifier methods.
I was having a similar problem. I had registered the default UITableViewCell class instead of my custom class.
I had this:
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: visibilityCellIdentifier)
but I should have had this:
tableView.registerClass(VisibilityCell.self, forCellReuseIdentifier: visibilityCellIdentifier)
The cell creation all looked to be working as I stepped through the debugger, but nothing showed up until I selected each row.
I get same issue when i use this code:
cell = [tableView dequeueReusableCellWithIdentifier:kCellSection1 forIndexPath:indexPath];
if (indexPath.row == 0) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellSection1];
cell.textLabel.text = #"Vote Up for me if it help for you!";
}
But I fix it by replace:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellSection1];
To:
cell = [cell initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellSection1];
Sometime it happens when you are using custom UITableViewCell with labels, images etc but also somewhere you are setting default UILabel of cell. for example
customCell.textLabel.text = #"some text"
To solve this problem, avoid using default label, instead add your own UILabel in your custom cell.