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 >
Related
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 a UICollectionview to show a lot of Custom cells (250 more or less).
Those cells have a main Image and some text. As the images have to be downloaded from the Internet I am using the external library AsyncImageView to do the lazy load stuff.
But the problem is that the reusable property of the cells are making me crazy.
When I scroll the images appear in the wrong cells. How can I add a tag or something to the images apart from the indexpath to avoid the problem?
Maybe AsyncImageView has a solution to the problem which I ignore ...
Or another alternative would be a better choice?
Any clue?
Thanks in advance
Edit: A simplified version of my code
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
CollectionComercioCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
if (cell == nil){
}
else{
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget: cell.myImage];
}
cell.myImage.imageURL = nil;
cell.myImage.image = nil;
cell.myImage.hidden = TRUE;
cell.myImage.imageURL = [[myArray objectAtIndex:indexPath.row] getLogoUrl];
cell.myText.text = [[myArray objectAtIndex:indexPath.row] getName];
cell.myImage.hidden = FALSE;
return cell;
}
CustomCell.m
- (void)prepareForReuse
{
[super prepareForReuse];
self.myImage.image = nil;
}
Make sure you set the image to nil in your cellForRowAtIndexPath: method. This way it will stay at least empty until the new image is loaded.
As you mentioned in your comments to the question that this is works if you scroll slow and not when you scroll fast you could maybe override - (void)prepareForReuse on your custom cell.
But be aware that Apple warns you not to use it for content changes:
For performance reasons, you should only reset attributes of the cell
that are not related to content, for example, alpha, editing, and
selection state. The table view's delegate in
tableView:cellForRowAtIndexPath: should always reset all content when
reusing a cell. If the cell object does not have an associated reuse
identifier, this method is not called. If you override this method,
you must be sure to invoke the superclass implementation.
I am having a problem with a UIImageView I am setting inside tableview:cellForRowAtIndexPath: for some reason when the UITableView loads the bottom cell half in view dose not have the a Image loaded into the UIImageView like the rest of the UITableViewCells, however once I scroll then it works itself out.. But once doing so the top UItableViewCell then drops its image! untill i scroll that back into full view.
What I have done is created the UITableViewCell in Interface Builder and i have a blank UIImageView, I then set the UIImage I am going to place inside the UIImageView in ViewDidLoad then inside tableview:CellForRowAtIndexPath: I set it then return the cell.
heres the code
//.m
- (void)viewDidLoad
{
//..
storageCalcIconImage = [UIImage imageNamed:#"CALC.png"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
SeriesSearchResultItem *myObj = [[SeriesSearchResultItem alloc] init];
if (indexPath.section == 0) {
//call obj array with all of the values from my sorting algorithum
myObj = (SeriesSearchResultItem*)[dataArrayOfObjects objectAtIndex:indexPath.row];
//.. assinging storageCalcIconImage inside if statment
firstSpaceImage.image = storageCalcIconImage;
//..
}
return cell;
}
there is lots happening in tableView:CellForRowAtIndexPath but I decided to drop the stuff that wasnt related to the image problem.. hopefully you guys can see an error I am making that I havent yet... any help would be greatly appreciated.
This is an expected result from the use of reusable cells inside a tableView. If you want to avoid this behavior you should create a cell for each rows of your tableView instead of using dequeueReusableCellWithIdentifier.
Expect huge performance impact if you have a lot of rows to display though.
Newbie question.
I have a custom UICollectionView cells with UIImage and UIButton in every cell. I need to have array of UIImages that will be filled after cell's checkbox will be tapped to "checked" state (custom UIButton with appropriate checked/unchecked images connected to IBAction with changing internal cell's BOOL and checkbox state image). I can't use -didSelectItemAtIndexPath: for marking because I'm using it for previewing a cell photo. First idea was to add cell image to NSMutableArray by action of tapping checkbox to "checked" state, but if user will want to uncheck checkbox I can't track what exactly UIImage I need to delete from array. What is most painful method to do this?
PS: it is a listing of my method connected to the checkbox
- (void)theCheckboxTapped:(UIButton*)sender
{
cell = (CustomCell *)sender.superview.superview;
if (cell)
{
if ([cell isSelected] == NO)
{
UIImage *img1 = [UIImage imageNamed:#"checked.png"];
[[(CustomCell *)cell checkboxSign] setImage:img1 forState:UIControlStateNormal];
}
if ([cell isSelected] == YES)
{
UIImage *img2 = [UIImage imageNamed:#"unchecked.png"];
[[(CustomCell *)cell checkboxSign] setImage:img2 forState:UIControlStateNormal];
}
cell.isSelected =! cell.isSelected;
}
}
First idea was to made array of cells and check does BOOl of this cell is equal to YES or NO and add appropriate cell images to array
For solving this there are many solutions. But the one which I am explaining, is easy but this consumes some little bit more memory than other methods.
Steps:-
Create two array with capacity cell count. add all images in this array and add only bool value false initially when no cell is select. For this you can add 0.
NSMutableArray *imageArray = [[NSMutableArray alloc]init];
NSMutableArray *boolArray = [[NSMutableArray alloc]init];
When you checked any cell button then replace 0 with 1 in boolArray array, and vice versa.
And reload table. During reloading if value in second array for that position is 0 then no need to pick image from second and if value is 1 then pick image.
for changing value you can replaceObjectAtIndex method of nsmutablearray.
Edit:
No need to do like above.
one simpler solution is according to your code here.
As I am seeing that you are able to get cell.
So you can set tag for imageView and depending to tag you can also get cell's imageview in which you want to add image.
For this create imageArray and all images in this. Now add image like:
if cell.selected = True;
cell.imageView.image = [imageArray objectAtIndex:#"Cell's tage here or buttons tag here which is equal to indexPath.row"];
else
cell.imageView.image = #"Default image or no image here".
i have solved in a simple way.
Only after load 50-60 image uiCollectionView became very slow, so i have write a method to remove all imageView from the existing cell.
-(void)cleanCache{
NSMutableArray *visibleThumbViews = [NSMutableArray arrayWithCapacity:self.collectionView.visibleCells.count];
for (ThumbCell *t in self.collectionView.visibleCells) {
[visibleThumbViews addObject:t.thumbView];
}
for (ThumbView *t in self.thumbsViews) {
if (![visibleThumbViews containsObject:t]) {
[t unloadImage];
[t removeFromSuperview];
}
}
}
this method is called here:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 30 == 0) {
[self cleanCache];
NSLog(#"clean");
}
ThumbCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"ThumbCellID" forIndexPath:indexPath];
cell.thumbView = [self.thumbsViews objectAtIndex:indexPath.row];
cell.thumbView.frame = cell.bounds;
return cell;
}
it's work very fine :P
sorry for bad english
UPDATE:
i had missed this delegate method:
-(void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
ThumbView *t = [self.thumbsViews objectAtIndex:indexPath.row];
[t unloadImage];
[t removeFromSuperview];
}
I have a UITableViewCell, it is scroll-disabled and with fixed sections and rows.
There are two sections, with 1 row in section 0, and several rows in section 1.
The tableView is for users to make some choices.
So, the first section (with only one row) is going to display the result of users' choices,
and no doubt the second section (with several rows) is for choosing.
Now I want to put an image in the cell of the only row of the first section,
and this image will change according to users' choose.
It is very easy to judge which png image should be displaying, but I have trouble update it.
I tried use the cell's imageView, and manually alloc a UIImageView or UIView there to display those images.
But all of them won't work, I mean they just keep what they are displaying at the beginning and never changes it, even if I set the view's background or its image to a new png.
I tried some method like
[myImage setNeedsDisplay] for the manually alloced view,
or
[thatCell setNeedsDiaplsy] & [self.tableView reloadData] for the imageView of that cell,
but in vain.
So I wonder how can I achieve this function that dynamically display an image in a UITableViewCell in different situations?
Thanks a lot!
_____update line_____
I'm sorry that I didn't provide my code.
and here they are.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *inOutTableCellIdentifier = #"DealTableViewIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:inOutTableCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:inOutTableCellIdentifier] autorelease];
cell.textLabel.font = [UIFont fontWithName:cMyFont size:[UIFont systemFontSize]];
cell.detailTextLabel.font = [UIFont fontWithName:cMyFont size:[UIFont smallSystemFontSize]];
// I tried using both imageView and UIView here, but won't work
if (indexPath.section == 0) {
//cell.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"moneyCell.png"]];
cell.backgroundColor = [UIColor cyanColor];
// cell.imageView.image = [UIImage imageNamed:#"dealDone2.png"];
self.undoneIcon = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)] autorelease];
//self.undoneIcon.image = [UIImage imageNamed:#"dealUndone2.png"];
self.undoneIcon.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"dealUndone2.png"]];
[cell.contentView addSubview:self.undoneIcon];
.... // deal with other rows in section 1
return cell;
}
// and this is the method that update the image, the images are named "dealDoneX.png",
// where X is an integer from 0 to 4.
- (void)checkUndoneDegree { // here I also tried 2 ways corresponding to UIView or a cell's imageView, but neither works well.
int i = 0;
if (self._date)
i++;
if (self.moneyTextField.text)
i++;
if (self._incomingAccount)
i++;
if (self._expensingAccount)
i++;
if (_dealType != kTransferView)
if (self._type)
i++;
NSLog(#"undone degree: %d",i);
NSString *imageName = [#"dealUndone" stringByAppendingFormat:#"%d",i];
self.undoneIcon.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:imageName]];
[self.undoneIcon setNeedsDisplay];
// NSUInteger p[2] = {0,0};
// UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:p length:2]];
// [cell setNeedsDisplay];
}
and everytime I update the table's data, like changing some text of some cell,
I would call [self checkUndoneDegree] and then call [self.tableView reloadData],
But the picture is never updated, at least from the screen.
I even tried to put the codes that decide which png to set in the
(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
method, but it can only make the view displaying the first png, without any updating.
Thanks for helping!
Make your undoneDegree (represented by i variable in your code) an ivar of your view controller, so it is accessible in all of it's methods, also in the UITableView delegate and data source methods.
Forget about setNeedsDisplay method. Since you are using UITableView to display your content, you need to play by its rules. This means you should use reloadSections:withRowAnimation: method.
Check again if you need self.undoneIcon. I'm pretty sure that imageView property should do. That is if you really want to display an image in the cell. If you want to create some kind of progress bar by manipulating cell's background, then use backgroundView property, or just place UIProgressView in your cell. This is what I think your want to do, after looking at your code.